Repository: embassy-rs/embassy Branch: main Commit: 165870484d2c Files: 2345 Total size: 12.5 MB Directory structure: gitextract_vlawd1i1/ ├── .gitattributes ├── .github/ │ ├── ci/ │ │ ├── book.sh │ │ ├── build-nightly.sh │ │ ├── build-xtensa.sh │ │ ├── build.sh │ │ ├── doc.sh │ │ ├── janitor.sh │ │ ├── rustfmt.sh │ │ ├── test-nightly.sh │ │ └── test.sh │ └── workflows/ │ ├── matrix-bot-issues.yml │ └── matrix-bot.yml ├── .gitignore ├── .helix/ │ └── languages.toml ├── .vscode/ │ ├── .gitignore │ ├── extensions.json │ └── settings.json ├── LICENSE-APACHE ├── LICENSE-CC-BY-SA ├── LICENSE-MIT ├── NOTICE.md ├── README.md ├── RELEASE.md ├── ci-nightly.sh ├── ci-xtensa.sh ├── ci.sh ├── cyw43/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── bluetooth.rs │ ├── consts.rs │ ├── control.rs │ ├── countries.rs │ ├── events.rs │ ├── fmt.rs │ ├── ioctl.rs │ ├── lib.rs │ ├── runner.rs │ ├── sdio.rs │ ├── spi.rs │ ├── structs.rs │ └── util.rs ├── cyw43-firmware/ │ ├── .gitignore │ ├── LICENSE-permissive-binary-license-1.0.txt │ ├── README.md │ └── write_nvrams.rs ├── cyw43-pio/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── docs/ │ ├── Makefile │ ├── README.md │ ├── examples/ │ │ ├── basic/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── memory.x │ │ │ └── src/ │ │ │ └── main.rs │ │ └── layer-by-layer/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── blinky-async/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── blinky-hal/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── blinky-irq/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── blinky-pac/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── memory.x │ ├── images/ │ │ ├── embassy_executor.drawio │ │ └── embassy_irq.drawio │ ├── index.adoc │ └── pages/ │ ├── basic_application.adoc │ ├── beginners.adoc │ ├── best_practices.adoc │ ├── bootloader.adoc │ ├── developer.adoc │ ├── developer_stm32.adoc │ ├── embassy_in_the_wild.adoc │ ├── examples.adoc │ ├── faq.adoc │ ├── getting_started.adoc │ ├── hal.adoc │ ├── imxrt.adoc │ ├── layer_by_layer.adoc │ ├── mcxa.adoc │ ├── microchip.adoc │ ├── new_project.adoc │ ├── nrf.adoc │ ├── overview.adoc │ ├── project_structure.adoc │ ├── runtime.adoc │ ├── sharing_peripherals.adoc │ ├── stm32.adoc │ ├── system.adoc │ └── time_keeping.adoc ├── embassy-boot/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── boot_loader.rs │ ├── digest_adapters/ │ │ ├── ed25519_dalek.rs │ │ ├── mod.rs │ │ └── salty.rs │ ├── firmware_updater/ │ │ ├── asynch.rs │ │ ├── blocking.rs │ │ └── mod.rs │ ├── fmt.rs │ ├── lib.rs │ ├── mem_flash.rs │ └── test_flash/ │ ├── asynch.rs │ ├── blocking.rs │ └── mod.rs ├── embassy-boot-nrf/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── fmt.rs │ └── lib.rs ├── embassy-boot-rp/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src/ │ ├── fmt.rs │ └── lib.rs ├── embassy-boot-stm32/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src/ │ ├── fmt.rs │ └── lib.rs ├── embassy-embedded-hal/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── adapter/ │ │ ├── blocking_async.rs │ │ ├── mod.rs │ │ └── yielding_async.rs │ ├── flash/ │ │ ├── concat_flash.rs │ │ ├── mem_flash.rs │ │ ├── mod.rs │ │ └── partition/ │ │ ├── asynch.rs │ │ ├── blocking.rs │ │ └── mod.rs │ ├── lib.rs │ └── shared_bus/ │ ├── asynch/ │ │ ├── i2c.rs │ │ ├── mod.rs │ │ └── spi.rs │ ├── blocking/ │ │ ├── i2c.rs │ │ ├── mod.rs │ │ └── spi.rs │ └── mod.rs ├── embassy-executor/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── build_common.rs │ ├── gen_config.py │ ├── src/ │ │ ├── fmt.rs │ │ ├── lib.rs │ │ ├── metadata.rs │ │ ├── platform/ │ │ │ ├── avr.rs │ │ │ ├── cortex_ar.rs │ │ │ ├── cortex_m.rs │ │ │ ├── riscv32.rs │ │ │ ├── spin.rs │ │ │ ├── std.rs │ │ │ └── wasm.rs │ │ ├── raw/ │ │ │ ├── deadline.rs │ │ │ ├── mod.rs │ │ │ ├── run_queue.rs │ │ │ ├── state_atomics.rs │ │ │ ├── state_atomics_arm.rs │ │ │ ├── state_critical_section.rs │ │ │ ├── trace.rs │ │ │ ├── util.rs │ │ │ ├── waker.rs │ │ │ └── waker_turbo.rs │ │ └── spawner.rs │ └── tests/ │ ├── test.rs │ ├── ui/ │ │ ├── abi.rs │ │ ├── abi.stderr │ │ ├── bad_return.rs │ │ ├── bad_return.stderr │ │ ├── bad_return_impl_future.rs │ │ ├── bad_return_impl_future.stderr │ │ ├── bad_return_impl_future_nightly.rs │ │ ├── bad_return_impl_future_nightly.stderr │ │ ├── generics.rs │ │ ├── generics.stderr │ │ ├── impl_trait.rs │ │ ├── impl_trait.stderr │ │ ├── impl_trait_nested.rs │ │ ├── impl_trait_nested.stderr │ │ ├── impl_trait_static.rs │ │ ├── impl_trait_static.stderr │ │ ├── nonstatic_ref_anon.rs │ │ ├── nonstatic_ref_anon.stderr │ │ ├── nonstatic_ref_anon_nested.rs │ │ ├── nonstatic_ref_anon_nested.stderr │ │ ├── nonstatic_ref_elided.rs │ │ ├── nonstatic_ref_elided.stderr │ │ ├── nonstatic_ref_generic.rs │ │ ├── nonstatic_ref_generic.stderr │ │ ├── nonstatic_struct_anon.rs │ │ ├── nonstatic_struct_anon.stderr │ │ ├── nonstatic_struct_elided.rs │ │ ├── nonstatic_struct_elided.stderr │ │ ├── nonstatic_struct_generic.rs │ │ ├── nonstatic_struct_generic.stderr │ │ ├── not_async.rs │ │ ├── not_async.stderr │ │ ├── return_impl_future_nonsend.rs │ │ ├── return_impl_future_nonsend.stderr │ │ ├── return_impl_send.rs │ │ ├── return_impl_send.stderr │ │ ├── return_impl_send_nightly.rs │ │ ├── return_impl_send_nightly.stderr │ │ ├── self.rs │ │ ├── self.stderr │ │ ├── self_ref.rs │ │ ├── self_ref.stderr │ │ ├── spawn_nonsend.rs │ │ ├── spawn_nonsend.stderr │ │ ├── task_safety_attribute.rs │ │ ├── type_error.rs │ │ ├── type_error.stderr │ │ ├── unsafe_op_in_unsafe_task.rs │ │ ├── unsafe_op_in_unsafe_task.stderr │ │ ├── where_clause.rs │ │ └── where_clause.stderr │ └── ui.rs ├── embassy-executor-macros/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── lib.rs │ ├── macros/ │ │ ├── main.rs │ │ ├── mod.rs │ │ └── task.rs │ └── util.rs ├── embassy-executor-timer-queue/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── embassy-futures/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── block_on.rs │ ├── fmt.rs │ ├── join.rs │ ├── lib.rs │ ├── select.rs │ └── yield_now.rs ├── embassy-hal-internal/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── build_common.rs │ └── src/ │ ├── atomic_ring_buffer.rs │ ├── drop.rs │ ├── fmt.rs │ ├── interrupt.rs │ ├── lib.rs │ ├── macros.rs │ ├── peripheral.rs │ └── ratio.rs ├── embassy-imxrt/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── chips/ │ │ ├── mimxrt633s.rs │ │ └── mimxrt685s.rs │ ├── clocks.rs │ ├── crc.rs │ ├── dma.rs │ ├── flexcomm/ │ │ ├── mod.rs │ │ ├── spi.rs │ │ └── uart.rs │ ├── fmt.rs │ ├── gpio.rs │ ├── iopctl.rs │ ├── lib.rs │ ├── rng.rs │ └── time_driver.rs ├── embassy-mcxa/ │ ├── .gitignore │ ├── Cargo.toml │ ├── DEVGUIDE.md │ ├── README.md │ └── src/ │ ├── adc.rs │ ├── cdog.rs │ ├── chips/ │ │ ├── mcxa2xx.rs │ │ ├── mcxa5xx.rs │ │ └── mod.rs │ ├── clkout.rs │ ├── clocks/ │ │ ├── config.rs │ │ ├── gate.rs │ │ ├── mod.rs │ │ ├── operator.rs │ │ ├── periph_helpers/ │ │ │ ├── mcxa2xx.rs │ │ │ ├── mcxa5xx.rs │ │ │ └── mod.rs │ │ ├── sleep.rs │ │ └── types.rs │ ├── config.rs │ ├── crc.rs │ ├── ctimer/ │ │ ├── capture.rs │ │ ├── mod.rs │ │ └── pwm.rs │ ├── dma.rs │ ├── executor.rs │ ├── flash.rs │ ├── gpio.rs │ ├── i2c/ │ │ ├── controller.rs │ │ ├── mod.rs │ │ └── target.rs │ ├── i3c/ │ │ ├── controller.rs │ │ └── mod.rs │ ├── inputmux.rs │ ├── lib.rs │ ├── lpuart/ │ │ ├── bbq.rs │ │ ├── blocking.rs │ │ ├── buffered.rs │ │ ├── dma.rs │ │ └── mod.rs │ ├── ostimer.rs │ ├── perf_counters.rs │ ├── reset_reason.rs │ ├── rtc/ │ │ ├── mcxa2xx.rs │ │ ├── mcxa5xx.rs │ │ └── mod.rs │ ├── spi/ │ │ ├── controller.rs │ │ └── mod.rs │ ├── trng.rs │ └── wwdt.rs ├── embassy-microchip/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── fmt.rs │ ├── gpio.rs │ ├── i2c.rs │ ├── lib.rs │ ├── pwm.rs │ ├── tach.rs │ ├── time_driver.rs │ └── uart.rs ├── embassy-mspm0/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── build_common.rs │ └── src/ │ ├── adc.rs │ ├── dma.rs │ ├── fmt.rs │ ├── gpio.rs │ ├── i2c.rs │ ├── i2c_target.rs │ ├── lib.rs │ ├── macros.rs │ ├── mathacl.rs │ ├── time_driver.rs │ ├── timer.rs │ ├── trng.rs │ ├── uart/ │ │ ├── buffered.rs │ │ └── mod.rs │ └── wwdt.rs ├── embassy-net/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── dns.rs │ ├── driver_util.rs │ ├── fmt.rs │ ├── icmp.rs │ ├── lib.rs │ ├── raw.rs │ ├── tcp.rs │ ├── time.rs │ └── udp.rs ├── embassy-net-adin1110/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── crc32.rs │ ├── crc8.rs │ ├── fmt.rs │ ├── lib.rs │ ├── mdio.rs │ ├── phy.rs │ └── regs.rs ├── embassy-net-driver/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── embassy-net-driver-channel/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── fmt.rs │ └── lib.rs ├── embassy-net-enc28j60/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── bank0.rs │ ├── bank1.rs │ ├── bank2.rs │ ├── bank3.rs │ ├── common.rs │ ├── fmt.rs │ ├── header.rs │ ├── lib.rs │ ├── macros.rs │ ├── phy.rs │ └── traits.rs ├── embassy-net-esp-hosted/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── control.rs │ ├── esp_hosted_config.proto │ ├── fmt.rs │ ├── iface.rs │ ├── ioctl.rs │ ├── lib.rs │ └── proto.rs ├── embassy-net-nrf91/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── context.rs │ ├── fmt.rs │ └── lib.rs ├── embassy-net-ppp/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── fmt.rs │ └── lib.rs ├── embassy-net-tuntap/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── embassy-net-wiznet/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── chip/ │ │ ├── mod.rs │ │ ├── w5100s.rs │ │ ├── w5500.rs │ │ ├── w6100.rs │ │ └── w6300.rs │ ├── device.rs │ └── lib.rs ├── embassy-nrf/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── buffered_uarte/ │ │ ├── mod.rs │ │ ├── v1.rs │ │ └── v2.rs │ ├── chips/ │ │ ├── nrf51.rs │ │ ├── nrf52805.rs │ │ ├── nrf52810.rs │ │ ├── nrf52811.rs │ │ ├── nrf52820.rs │ │ ├── nrf52832.rs │ │ ├── nrf52833.rs │ │ ├── nrf52840.rs │ │ ├── nrf5340_app.rs │ │ ├── nrf5340_net.rs │ │ ├── nrf54l05_app.rs │ │ ├── nrf54l10_app.rs │ │ ├── nrf54l15_app.rs │ │ ├── nrf54lm20_app.rs │ │ ├── nrf9120.rs │ │ └── nrf9160.rs │ ├── cracen.rs │ ├── cryptocell_rng.rs │ ├── egu.rs │ ├── embassy_net_802154_driver.rs │ ├── fmt.rs │ ├── gpio.rs │ ├── gpiote.rs │ ├── i2s.rs │ ├── ipc.rs │ ├── lib.rs │ ├── nfct.rs │ ├── nvmc.rs │ ├── pdm.rs │ ├── power.rs │ ├── ppi/ │ │ ├── dppi.rs │ │ ├── mod.rs │ │ └── ppi.rs │ ├── pwm.rs │ ├── qdec.rs │ ├── qspi.rs │ ├── radio/ │ │ ├── ieee802154.rs │ │ └── mod.rs │ ├── reset.rs │ ├── rng.rs │ ├── rramc.rs │ ├── rtc.rs │ ├── saadc.rs │ ├── spim.rs │ ├── spis.rs │ ├── temp.rs │ ├── time_driver.rs │ ├── timer.rs │ ├── twim.rs │ ├── twis.rs │ ├── uarte.rs │ ├── usb/ │ │ ├── mod.rs │ │ └── vbus_detect.rs │ ├── util.rs │ └── wdt.rs ├── embassy-nxp/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── build.rs │ ├── build_common.rs │ └── src/ │ ├── chips/ │ │ ├── lpc55.rs │ │ ├── mimxrt1011.rs │ │ └── mimxrt1062.rs │ ├── dma/ │ │ └── lpc55.rs │ ├── dma.rs │ ├── fmt.rs │ ├── gpio/ │ │ ├── lpc55.rs │ │ └── rt1xxx.rs │ ├── gpio.rs │ ├── iomuxc.rs │ ├── lib.rs │ ├── pint.rs │ ├── pwm/ │ │ └── lpc55.rs │ ├── pwm.rs │ ├── sct.rs │ ├── time_driver/ │ │ ├── pit.rs │ │ └── rtc.rs │ ├── usart/ │ │ └── lpc55.rs │ └── usart.rs ├── embassy-rp/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── build.rs │ ├── funcsel.txt │ ├── link-rp.x.in │ └── src/ │ ├── adc.rs │ ├── aon_timer/ │ │ └── mod.rs │ ├── block.rs │ ├── bootsel.rs │ ├── clocks.rs │ ├── critical_section_impl.rs │ ├── datetime/ │ │ ├── datetime_chrono.rs │ │ ├── datetime_no_deps.rs │ │ ├── epoch.rs │ │ └── mod.rs │ ├── dma.rs │ ├── executor.rs │ ├── flash.rs │ ├── float/ │ │ ├── add_sub.rs │ │ ├── cmp.rs │ │ ├── conv.rs │ │ ├── div.rs │ │ ├── functions.rs │ │ ├── mod.rs │ │ └── mul.rs │ ├── fmt.rs │ ├── gpio.rs │ ├── i2c.rs │ ├── i2c_slave.rs │ ├── intrinsics.rs │ ├── lib.rs │ ├── multicore.rs │ ├── otp.rs │ ├── pio/ │ │ ├── instr.rs │ │ └── mod.rs │ ├── pio_programs/ │ │ ├── clk.rs │ │ ├── clock_divider.rs │ │ ├── hd44780.rs │ │ ├── i2s.rs │ │ ├── mod.rs │ │ ├── onewire.rs │ │ ├── pwm.rs │ │ ├── rotary_encoder.rs │ │ ├── spi.rs │ │ ├── stepper.rs │ │ ├── uart.rs │ │ └── ws2812.rs │ ├── psram.rs │ ├── pwm.rs │ ├── qmi_cs1.rs │ ├── relocate.rs │ ├── reset.rs │ ├── rom_data/ │ │ ├── mod.rs │ │ ├── rp2040.rs │ │ └── rp235x.rs │ ├── rtc/ │ │ ├── conversions.rs │ │ ├── filter.rs │ │ └── mod.rs │ ├── spi.rs │ ├── spinlock.rs │ ├── spinlock_mutex.rs │ ├── time_driver.rs │ ├── trng.rs │ ├── uart/ │ │ ├── buffered.rs │ │ └── mod.rs │ ├── usb.rs │ └── watchdog.rs ├── embassy-stm32/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── build_common.rs │ └── src/ │ ├── adc/ │ │ ├── adc4.rs │ │ ├── c0.rs │ │ ├── f1.rs │ │ ├── f3.rs │ │ ├── f3_v1_1.rs │ │ ├── g4.rs │ │ ├── injected.rs │ │ ├── mod.rs │ │ ├── ringbuffered.rs │ │ ├── v1.rs │ │ ├── v2.rs │ │ ├── v3.rs │ │ ├── v4.rs │ │ └── watchdog_v1.rs │ ├── aes/ │ │ └── mod.rs │ ├── backup_sram.rs │ ├── can/ │ │ ├── bxcan/ │ │ │ ├── filter.rs │ │ │ ├── mod.rs │ │ │ └── registers.rs │ │ ├── common.rs │ │ ├── enums.rs │ │ ├── fd/ │ │ │ ├── config.rs │ │ │ ├── filter.rs │ │ │ ├── message_ram/ │ │ │ │ ├── common.rs │ │ │ │ ├── enums.rs │ │ │ │ ├── extended_filter.rs │ │ │ │ ├── generic.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── rxfifo_element.rs │ │ │ │ ├── standard_filter.rs │ │ │ │ ├── txbuffer_element.rs │ │ │ │ └── txevent_element.rs │ │ │ ├── mod.rs │ │ │ └── peripheral.rs │ │ ├── fdcan.rs │ │ ├── frame.rs │ │ ├── mod.rs │ │ └── util.rs │ ├── comp.rs │ ├── cordic/ │ │ ├── enums.rs │ │ ├── errors.rs │ │ ├── mod.rs │ │ └── utils.rs │ ├── cpu.rs │ ├── crc/ │ │ ├── mod.rs │ │ ├── v1.rs │ │ └── v2v3.rs │ ├── cryp/ │ │ └── mod.rs │ ├── dac/ │ │ └── mod.rs │ ├── dcmi.rs │ ├── dma/ │ │ ├── dma_bdma.rs │ │ ├── dmamux.rs │ │ ├── gpdma/ │ │ │ ├── linked_list.rs │ │ │ ├── mod.rs │ │ │ └── ringbuffered.rs │ │ ├── mod.rs │ │ ├── ringbuffer/ │ │ │ ├── mod.rs │ │ │ └── tests/ │ │ │ ├── mod.rs │ │ │ └── prop_test/ │ │ │ ├── mod.rs │ │ │ ├── reader.rs │ │ │ └── writer.rs │ │ ├── util.rs │ │ └── word.rs │ ├── dsihost.rs │ ├── dts/ │ │ ├── mod.rs │ │ └── tsel.rs │ ├── eth/ │ │ ├── generic_phy.rs │ │ ├── mod.rs │ │ ├── sma/ │ │ │ ├── mod.rs │ │ │ ├── v1.rs │ │ │ └── v2.rs │ │ ├── v1/ │ │ │ ├── mod.rs │ │ │ ├── rx_desc.rs │ │ │ └── tx_desc.rs │ │ └── v2/ │ │ ├── descriptors.rs │ │ └── mod.rs │ ├── executor.rs │ ├── exti/ │ │ ├── blocking.rs │ │ ├── low_level.rs │ │ └── mod.rs │ ├── flash/ │ │ ├── asynch.rs │ │ ├── c.rs │ │ ├── common.rs │ │ ├── eeprom.rs │ │ ├── f0.rs │ │ ├── f1f3.rs │ │ ├── f2.rs │ │ ├── f4.rs │ │ ├── f7.rs │ │ ├── g.rs │ │ ├── h5.rs │ │ ├── h50.rs │ │ ├── h7.rs │ │ ├── l.rs │ │ ├── mod.rs │ │ ├── other.rs │ │ ├── u0.rs │ │ └── u5.rs │ ├── fmc.rs │ ├── fmt.rs │ ├── gpio.rs │ ├── hash/ │ │ └── mod.rs │ ├── hrtim/ │ │ ├── bridge_converter.rs │ │ ├── mod.rs │ │ ├── resonant_converter.rs │ │ └── traits.rs │ ├── hsem/ │ │ └── mod.rs │ ├── hspi/ │ │ ├── enums.rs │ │ └── mod.rs │ ├── i2c/ │ │ ├── config.rs │ │ ├── mod.rs │ │ ├── v1.rs │ │ └── v2.rs │ ├── i2s.rs │ ├── ipcc.rs │ ├── lcd.rs │ ├── lib.rs │ ├── low_power.rs │ ├── lptim/ │ │ ├── channel.rs │ │ ├── mod.rs │ │ ├── pwm.rs │ │ └── timer/ │ │ ├── channel_direction.rs │ │ ├── mod.rs │ │ └── prescaler.rs │ ├── ltdc.rs │ ├── macros.rs │ ├── opamp.rs │ ├── ospi/ │ │ ├── enums.rs │ │ └── mod.rs │ ├── pka/ │ │ └── mod.rs │ ├── qspi/ │ │ ├── enums.rs │ │ └── mod.rs │ ├── rcc/ │ │ ├── bd.rs │ │ ├── c0.rs │ │ ├── f013.rs │ │ ├── f247.rs │ │ ├── g0.rs │ │ ├── g4.rs │ │ ├── h.rs │ │ ├── hsi48.rs │ │ ├── l.rs │ │ ├── mco.rs │ │ ├── mod.rs │ │ ├── n6.rs │ │ ├── u3.rs │ │ ├── u5.rs │ │ └── wba.rs │ ├── rng.rs │ ├── rtc/ │ │ ├── datetime.rs │ │ ├── low_power.rs │ │ ├── mod.rs │ │ ├── v2.rs │ │ └── v3.rs │ ├── saes/ │ │ └── mod.rs │ ├── sai/ │ │ └── mod.rs │ ├── sdmmc/ │ │ ├── mod.rs │ │ ├── sd.rs │ │ └── sdio.rs │ ├── spdifrx/ │ │ └── mod.rs │ ├── spi/ │ │ └── mod.rs │ ├── time.rs │ ├── time_driver/ │ │ ├── gp16.rs │ │ ├── lptim.rs │ │ └── mod.rs │ ├── timer/ │ │ ├── complementary_pwm.rs │ │ ├── input_capture.rs │ │ ├── low_level.rs │ │ ├── mod.rs │ │ ├── one_pulse.rs │ │ ├── pwm_input.rs │ │ ├── qei.rs │ │ ├── ringbuffered.rs │ │ └── simple_pwm.rs │ ├── tsc/ │ │ ├── acquisition_banks.rs │ │ ├── config.rs │ │ ├── errors.rs │ │ ├── io_pin.rs │ │ ├── mod.rs │ │ ├── pin_groups.rs │ │ ├── tsc.rs │ │ └── types.rs │ ├── ucpd.rs │ ├── uid.rs │ ├── usart/ │ │ ├── buffered.rs │ │ ├── mod.rs │ │ └── ringbuffered.rs │ ├── usb/ │ │ ├── mod.rs │ │ ├── otg.rs │ │ └── usb.rs │ ├── vrefbuf/ │ │ └── mod.rs │ ├── wdg/ │ │ └── mod.rs │ └── xspi/ │ ├── enums.rs │ └── mod.rs ├── embassy-stm32-wpan/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── src/ │ │ ├── lib.rs │ │ ├── wb55/ │ │ │ ├── channels.rs │ │ │ ├── cmd.rs │ │ │ ├── consts.rs │ │ │ ├── evt.rs │ │ │ ├── fmt.rs │ │ │ ├── fus.rs │ │ │ ├── lhci.rs │ │ │ ├── mac/ │ │ │ │ ├── commands.rs │ │ │ │ ├── consts.rs │ │ │ │ ├── control.rs │ │ │ │ ├── driver.rs │ │ │ │ ├── event.rs │ │ │ │ ├── indications.rs │ │ │ │ ├── macros.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── opcodes.rs │ │ │ │ ├── responses.rs │ │ │ │ ├── runner.rs │ │ │ │ └── typedefs.rs │ │ │ ├── mod.rs │ │ │ ├── shci.rs │ │ │ ├── sub/ │ │ │ │ ├── ble.rs │ │ │ │ ├── mac.rs │ │ │ │ ├── mm.rs │ │ │ │ ├── mod.rs │ │ │ │ └── sys.rs │ │ │ ├── tables.rs │ │ │ └── unsafe_linked_list.rs │ │ └── wba/ │ │ ├── RNG_INTEGRATION.md │ │ ├── bindings.rs │ │ ├── ble.rs │ │ ├── context.rs │ │ ├── error.rs │ │ ├── gap/ │ │ │ ├── advertiser.rs │ │ │ ├── connection.rs │ │ │ ├── mod.rs │ │ │ ├── scanner.rs │ │ │ └── types.rs │ │ ├── gatt/ │ │ │ ├── events.rs │ │ │ ├── mod.rs │ │ │ ├── server.rs │ │ │ └── types.rs │ │ ├── hci/ │ │ │ ├── command.rs │ │ │ ├── event.rs │ │ │ ├── host_if.rs │ │ │ ├── mod.rs │ │ │ └── types.rs │ │ ├── linklayer_plat.rs │ │ ├── ll_sys/ │ │ │ ├── ll_sys_cs.rs │ │ │ ├── ll_sys_dp_slp.rs │ │ │ ├── ll_sys_intf.rs │ │ │ ├── ll_sys_startup.rs │ │ │ ├── ll_version.rs │ │ │ └── mod.rs │ │ ├── ll_sys_if.rs │ │ ├── mac_sys_if.rs │ │ ├── mod.rs │ │ ├── power_table.rs │ │ ├── runner.rs │ │ ├── security/ │ │ │ └── mod.rs │ │ └── util_seq.rs │ ├── tl_mbox.x.in │ ├── tl_mbox_extended_wb1.x.in │ └── tl_mbox_extended_wbx5.x.in ├── embassy-sync/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── build_common.rs │ ├── src/ │ │ ├── blocking_mutex/ │ │ │ ├── mod.rs │ │ │ └── raw.rs │ │ ├── channel.rs │ │ ├── fmt.rs │ │ ├── lazy_lock.rs │ │ ├── lib.rs │ │ ├── mutex.rs │ │ ├── once_lock.rs │ │ ├── pipe.rs │ │ ├── priority_channel.rs │ │ ├── pubsub/ │ │ │ ├── mod.rs │ │ │ ├── publisher.rs │ │ │ └── subscriber.rs │ │ ├── ring_buffer.rs │ │ ├── rwlock.rs │ │ ├── semaphore.rs │ │ ├── signal.rs │ │ ├── waitqueue/ │ │ │ ├── atomic_waker.rs │ │ │ ├── atomic_waker_turbo.rs │ │ │ ├── mod.rs │ │ │ ├── multi_waker.rs │ │ │ └── waker_registration.rs │ │ ├── watch.rs │ │ └── zerocopy_channel.rs │ └── tests/ │ ├── ui/ │ │ └── sync_impl/ │ │ ├── lazy_lock_function.rs │ │ ├── lazy_lock_function.stderr │ │ ├── lazy_lock_type.rs │ │ ├── lazy_lock_type.stderr │ │ ├── once_lock.rs │ │ └── once_lock.stderr │ └── ui.rs ├── embassy-time/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── delay.rs │ ├── driver_mock.rs │ ├── driver_std.rs │ ├── driver_wasm.rs │ ├── duration.rs │ ├── fmt.rs │ ├── instant.rs │ ├── lib.rs │ └── timer.rs ├── embassy-time-driver/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── gen_tick.py │ └── src/ │ ├── lib.rs │ └── tick.rs ├── embassy-time-queue-utils/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src/ │ ├── lib.rs │ ├── queue_generic.rs │ └── queue_integrated.rs ├── embassy-usb/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── gen_config.py │ └── src/ │ ├── builder.rs │ ├── class/ │ │ ├── cdc_acm.rs │ │ ├── cdc_ncm/ │ │ │ ├── embassy_net.rs │ │ │ └── mod.rs │ │ ├── cmsis_dap_v2.rs │ │ ├── dfu/ │ │ │ ├── app_mode.rs │ │ │ ├── consts.rs │ │ │ ├── dfu_mode.rs │ │ │ └── mod.rs │ │ ├── hid.rs │ │ ├── midi.rs │ │ ├── mod.rs │ │ ├── uac1/ │ │ │ ├── class_codes.rs │ │ │ ├── mod.rs │ │ │ ├── speaker.rs │ │ │ └── terminal_type.rs │ │ └── web_usb.rs │ ├── control.rs │ ├── descriptor.rs │ ├── descriptor_reader.rs │ ├── fmt.rs │ ├── lib.rs │ ├── msos.rs │ └── types.rs ├── embassy-usb-dfu/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── src/ │ │ ├── application.rs │ │ ├── dfu.rs │ │ ├── fmt.rs │ │ └── lib.rs │ └── tests/ │ └── usb_dfu_test.rs ├── embassy-usb-driver/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── embassy-usb-logger/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── embassy-usb-synopsys-otg/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── fmt.rs │ ├── lib.rs │ └── otg_v1.rs ├── examples/ │ ├── .cargo/ │ │ └── config.toml │ ├── boot/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── application/ │ │ │ ├── nrf/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory-bl-nrf91.x │ │ │ │ ├── memory-bl.x │ │ │ │ ├── memory-nrf91.x │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── rp/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32f3/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32f7/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── flash-boot.sh │ │ │ │ ├── memory-bl.x │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32h7/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── flash-boot.sh │ │ │ │ ├── memory-bl.x │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32l0/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32l1/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32l4/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ └── src/ │ │ │ │ └── bin/ │ │ │ │ ├── a.rs │ │ │ │ └── b.rs │ │ │ ├── stm32wb-dfu/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ ├── secrets/ │ │ │ │ │ └── key.sec │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── stm32wba-dfu/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── build.rs │ │ │ │ ├── memory.x │ │ │ │ ├── secrets/ │ │ │ │ │ └── key.sec │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── stm32wl/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── memory.x │ │ │ └── src/ │ │ │ └── bin/ │ │ │ ├── a.rs │ │ │ └── b.rs │ │ └── bootloader/ │ │ ├── nrf/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── memory-bm.x │ │ │ ├── memory-s140.x │ │ │ ├── memory.x │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── rp/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── memory.x │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── stm32/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── memory.x │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── stm32-dual-bank/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── memory.x │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── stm32wb-dfu/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── memory.x │ │ │ ├── secrets/ │ │ │ │ └── key.pub.short │ │ │ └── src/ │ │ │ └── main.rs │ │ └── stm32wba-dfu/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ ├── secrets/ │ │ │ └── key.pub.short │ │ └── src/ │ │ └── main.rs │ ├── lpc55s69/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky_embassy_time.rs │ │ ├── blinky_nop.rs │ │ ├── button_executor.rs │ │ ├── pwm.rs │ │ ├── usart_async.rs │ │ └── usart_blocking.rs │ ├── mcxa2xx/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc-async.rs │ │ ├── adc-blocking.rs │ │ ├── adc-temperature.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_async.rs │ │ ├── capture.rs │ │ ├── cdog.rs │ │ ├── clkout.rs │ │ ├── cpu-clocks.rs │ │ ├── crc.rs │ │ ├── dma_mem_to_mem.rs │ │ ├── dma_scatter_gather_builder.rs │ │ ├── flash_iap.rs │ │ ├── hello.rs │ │ ├── i2c-async.rs │ │ ├── i2c-blocking.rs │ │ ├── i2c-dma.rs │ │ ├── i2c-scan-blocking.rs │ │ ├── i2c-target-async.rs │ │ ├── i2c-target-blocking.rs │ │ ├── i2c-target-dma.rs │ │ ├── i3c-async.rs │ │ ├── i3c-blocking.rs │ │ ├── i3c-dma.rs │ │ ├── lpuart_bbq_rx.rs │ │ ├── lpuart_bbq_tx.rs │ │ ├── lpuart_buffered.rs │ │ ├── lpuart_dma.rs │ │ ├── lpuart_polling.rs │ │ ├── lpuart_ring_buffer.rs │ │ ├── power-deepsleep-big-jump.rs │ │ ├── power-deepsleep-gating.rs │ │ ├── power-deepsleep-gpio-int.rs │ │ ├── power-deepsleep.rs │ │ ├── power-wfe-gated.rs │ │ ├── pwm.rs │ │ ├── reset-reason.rs │ │ ├── rtc_alarm.rs │ │ ├── spi-async.rs │ │ ├── spi-blocking.rs │ │ ├── spi-dma.rs │ │ ├── trng.rs │ │ ├── wwdt_interrupt.rs │ │ └── wwdt_reset.rs │ ├── mcxa5xx/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc-async.rs │ │ ├── adc-blocking.rs │ │ ├── adc-temperature.rs │ │ ├── blinky-firc.rs │ │ ├── blinky-sosc.rs │ │ ├── blinky-spll.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_async.rs │ │ ├── capture.rs │ │ ├── cdog.rs │ │ ├── clkout.rs │ │ ├── crc.rs │ │ ├── dma_mem_to_mem.rs │ │ ├── dma_scatter_gather_builder.rs │ │ ├── hello.rs │ │ ├── i2c-async.rs │ │ ├── i2c-blocking.rs │ │ ├── i2c-dma.rs │ │ ├── i2c-scan-blocking.rs │ │ ├── i2c-target-async.rs │ │ ├── i2c-target-blocking.rs │ │ ├── i2c-target-dma.rs │ │ ├── i3c-async.rs │ │ ├── i3c-blocking.rs │ │ ├── i3c-dma.rs │ │ ├── lpuart_bbq_rx.rs │ │ ├── lpuart_bbq_tx.rs │ │ ├── lpuart_buffered.rs │ │ ├── lpuart_dma.rs │ │ ├── lpuart_polling.rs │ │ ├── lpuart_ring_buffer.rs │ │ ├── power-deepsleep-big-jump.rs │ │ ├── power-deepsleep-gating.rs │ │ ├── power-deepsleep-gpio-int.rs │ │ ├── power-deepsleep.rs │ │ ├── power-wfe-gated.rs │ │ ├── pwm.rs │ │ ├── reset-reason.rs │ │ ├── rtc-alarm.rs │ │ ├── spi-async.rs │ │ ├── spi-blocking.rs │ │ ├── spi-dma.rs │ │ ├── trng.rs │ │ ├── wwdt_interrupt.rs │ │ └── wwdt_reset.rs │ ├── microchip/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── link_ram.x │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button-async.rs │ │ ├── button.rs │ │ ├── i2c-async.rs │ │ ├── i2c.rs │ │ ├── pwm.rs │ │ ├── uart_async.rs │ │ └── uart_blocking.rs │ ├── mimxrt1011/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ ├── bin/ │ │ │ ├── blinky.rs │ │ │ └── button.rs │ │ └── lib.rs │ ├── mimxrt1062-evk/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ ├── bin/ │ │ │ ├── blinky.rs │ │ │ └── button.rs │ │ └── lib.rs │ ├── mimxrt6/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ ├── bin/ │ │ │ ├── blinky.rs │ │ │ ├── button.rs │ │ │ ├── crc.rs │ │ │ ├── dma.rs │ │ │ ├── hello.rs │ │ │ ├── rng.rs │ │ │ ├── spi-async.rs │ │ │ ├── spi.rs │ │ │ ├── uart-async.rs │ │ │ └── uart.rs │ │ └── lib.rs │ ├── mspm0c1104/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── uart.rs │ │ └── wwdt.rs │ ├── mspm0g3507/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── i2c.rs │ │ ├── i2c_async.rs │ │ ├── i2c_target.rs │ │ ├── mathacl_ops.rs │ │ ├── uart.rs │ │ ├── uart_buffered.rs │ │ └── wwdt.rs │ ├── mspm0g3519/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── uart.rs │ │ └── wwdt.rs │ ├── mspm0g5187/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button.rs │ │ └── wwdt.rs │ ├── mspm0l1306/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── i2c.rs │ │ ├── i2c_async.rs │ │ ├── i2c_target.rs │ │ ├── uart.rs │ │ └── wwdt.rs │ ├── mspm0l2228/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── trng.rs │ │ ├── uart.rs │ │ └── wwdt.rs │ ├── nrf-rtos-trace/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── rtos_trace.rs │ ├── nrf51/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── blinky.rs │ ├── nrf52810/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── saadc_lowpower.rs │ ├── nrf52840/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── buffered_uart.rs │ │ ├── channel.rs │ │ ├── channel_sender_receiver.rs │ │ ├── egu.rs │ │ ├── ethernet_enc28j60.rs │ │ ├── executor_fairness_test.rs │ │ ├── gpiote_channel.rs │ │ ├── gpiote_port.rs │ │ ├── i2s_effect.rs │ │ ├── i2s_monitor.rs │ │ ├── i2s_waveform.rs │ │ ├── ieee802154_receive.rs │ │ ├── ieee802154_send.rs │ │ ├── manually_create_executor.rs │ │ ├── multiprio.rs │ │ ├── mutex.rs │ │ ├── nfct.rs │ │ ├── nvmc.rs │ │ ├── pdm.rs │ │ ├── pdm_continuous.rs │ │ ├── ppi.rs │ │ ├── pubsub.rs │ │ ├── pwm.rs │ │ ├── pwm_double_sequence.rs │ │ ├── pwm_sequence.rs │ │ ├── pwm_sequence_ppi.rs │ │ ├── pwm_sequence_ws2812b.rs │ │ ├── pwm_servo.rs │ │ ├── qdec.rs │ │ ├── qspi.rs │ │ ├── qspi_lowpower.rs │ │ ├── raw_spawn.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── saadc.rs │ │ ├── saadc_continuous.rs │ │ ├── self_spawn.rs │ │ ├── self_spawn_current_executor.rs │ │ ├── sixlowpan.rs │ │ ├── spim.rs │ │ ├── spis.rs │ │ ├── temp.rs │ │ ├── timer.rs │ │ ├── twim.rs │ │ ├── twim_lowpower.rs │ │ ├── twis.rs │ │ ├── uart.rs │ │ ├── uart_idle.rs │ │ ├── uart_split.rs │ │ ├── usb_ethernet.rs │ │ ├── usb_hid_keyboard.rs │ │ ├── usb_hid_mouse.rs │ │ ├── usb_serial.rs │ │ ├── usb_serial_multitask.rs │ │ ├── usb_serial_winusb.rs │ │ ├── wdt.rs │ │ └── wifi_esp_hosted.rs │ ├── nrf52840-edf/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── basic.rs │ ├── nrf52840-rtic/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── blinky.rs │ ├── nrf5340/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── cryptocell_rng.rs │ │ ├── gpiote_channel.rs │ │ ├── nrf5340dk_internal_caps.rs │ │ └── uart.rs │ ├── nrf54l15/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── buffered_uart.rs │ │ ├── gpiote_channel.rs │ │ ├── gpiote_port.rs │ │ ├── pwm.rs │ │ ├── rng.rs │ │ ├── rramc.rs │ │ ├── rramc_buffered.rs │ │ ├── saadc.rs │ │ ├── spim.rs │ │ ├── temp.rs │ │ ├── timer.rs │ │ ├── twim.rs │ │ ├── twis.rs │ │ ├── uart.rs │ │ └── wdt.rs │ ├── nrf54lm20/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── gpio.rs │ │ ├── gpiote_channel.rs │ │ ├── gpiote_port.rs │ │ └── timer.rs │ ├── nrf9151/ │ │ ├── ns/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── flash_tfm.sh │ │ │ ├── memory.x │ │ │ ├── src/ │ │ │ │ └── bin/ │ │ │ │ ├── blinky.rs │ │ │ │ └── uart.rs │ │ │ └── tfm.hex │ │ └── s/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── cryptocell_rng.rs │ ├── nrf9160/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── modem_tcp_client.rs │ ├── rp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── assets/ │ │ │ └── ferris.raw │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── assign_resources.rs │ │ ├── blinky.rs │ │ ├── blinky_two_channels.rs │ │ ├── blinky_two_tasks.rs │ │ ├── button.rs │ │ ├── debounce.rs │ │ ├── ethernet_w5500_icmp.rs │ │ ├── ethernet_w5500_icmp_ping.rs │ │ ├── ethernet_w5500_multisocket.rs │ │ ├── ethernet_w5500_tcp_client.rs │ │ ├── ethernet_w5500_tcp_server.rs │ │ ├── ethernet_w5500_udp.rs │ │ ├── ethernet_w55rp20_tcp_server.rs │ │ ├── flash.rs │ │ ├── gpio_async.rs │ │ ├── gpout.rs │ │ ├── i2c_async.rs │ │ ├── i2c_async_embassy.rs │ │ ├── i2c_blocking.rs │ │ ├── i2c_slave.rs │ │ ├── interrupt.rs │ │ ├── multicore.rs │ │ ├── multiprio.rs │ │ ├── orchestrate_tasks.rs │ │ ├── overclock.rs │ │ ├── overclock_manual.rs │ │ ├── pio_async.rs │ │ ├── pio_clk.rs │ │ ├── pio_dma.rs │ │ ├── pio_hd44780.rs │ │ ├── pio_i2s.rs │ │ ├── pio_i2s_mclk.rs │ │ ├── pio_onewire.rs │ │ ├── pio_onewire_parasite.rs │ │ ├── pio_pwm.rs │ │ ├── pio_rotary_encoder.rs │ │ ├── pio_servo.rs │ │ ├── pio_spi.rs │ │ ├── pio_spi_async.rs │ │ ├── pio_stepper.rs │ │ ├── pio_uart.rs │ │ ├── pio_ws2812.rs │ │ ├── pwm.rs │ │ ├── pwm_input.rs │ │ ├── rosc.rs │ │ ├── rtc.rs │ │ ├── rtc_alarm.rs │ │ ├── shared_bus.rs │ │ ├── sharing.rs │ │ ├── spi.rs │ │ ├── spi_async.rs │ │ ├── spi_display.rs │ │ ├── spi_gc9a01.rs │ │ ├── spi_sdmmc.rs │ │ ├── uart.rs │ │ ├── uart_buffered_split.rs │ │ ├── uart_r503.rs │ │ ├── uart_unidir.rs │ │ ├── usb_ethernet.rs │ │ ├── usb_hid_keyboard.rs │ │ ├── usb_hid_mouse.rs │ │ ├── usb_logger.rs │ │ ├── usb_midi.rs │ │ ├── usb_raw.rs │ │ ├── usb_raw_bulk.rs │ │ ├── usb_serial.rs │ │ ├── usb_serial_with_handler.rs │ │ ├── usb_serial_with_logger.rs │ │ ├── usb_webusb.rs │ │ ├── watchdog.rs │ │ ├── wifi_ap_tcp_server.rs │ │ ├── wifi_blinky.rs │ │ ├── wifi_scan.rs │ │ ├── wifi_tcp_server.rs │ │ ├── wifi_webrequest.rs │ │ └── zerocopy.rs │ ├── rp235x/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── assets/ │ │ │ └── ferris.raw │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── aon_timer_async.rs │ │ ├── assign_resources.rs │ │ ├── blinky.rs │ │ ├── blinky_two_channels.rs │ │ ├── blinky_two_tasks.rs │ │ ├── blinky_wifi.rs │ │ ├── blinky_wifi_pico_plus_2.rs │ │ ├── button.rs │ │ ├── debounce.rs │ │ ├── ethernet_w5500_icmp.rs │ │ ├── ethernet_w5500_icmp_ping.rs │ │ ├── ethernet_w5500_multisocket.rs │ │ ├── ethernet_w5500_tcp_client.rs │ │ ├── ethernet_w5500_tcp_server.rs │ │ ├── ethernet_w5500_udp.rs │ │ ├── ethernet_w6300_udp.rs │ │ ├── flash.rs │ │ ├── gpio_async.rs │ │ ├── gpout.rs │ │ ├── i2c_async.rs │ │ ├── i2c_async_embassy.rs │ │ ├── i2c_blocking.rs │ │ ├── i2c_slave.rs │ │ ├── interrupt.rs │ │ ├── multicore.rs │ │ ├── multicore_stack_overflow.rs │ │ ├── multiprio.rs │ │ ├── otp.rs │ │ ├── overclock.rs │ │ ├── pio_async.rs │ │ ├── pio_dma.rs │ │ ├── pio_hd44780.rs │ │ ├── pio_i2s.rs │ │ ├── pio_i2s_rx.rs │ │ ├── pio_onewire.rs │ │ ├── pio_onewire_parasite.rs │ │ ├── pio_pwm.rs │ │ ├── pio_rotary_encoder.rs │ │ ├── pio_rotary_encoder_rxf.rs │ │ ├── pio_servo.rs │ │ ├── pio_stepper.rs │ │ ├── pio_uart.rs │ │ ├── pio_ws2812.rs │ │ ├── psram.rs │ │ ├── pwm.rs │ │ ├── pwm_input.rs │ │ ├── pwm_tb6612fng_motor_driver.rs │ │ ├── rosc.rs │ │ ├── shared_bus.rs │ │ ├── sharing.rs │ │ ├── spi.rs │ │ ├── spi_async.rs │ │ ├── spi_display.rs │ │ ├── spi_sdmmc.rs │ │ ├── trng.rs │ │ ├── uart.rs │ │ ├── uart_buffered_split.rs │ │ ├── uart_r503.rs │ │ ├── uart_unidir.rs │ │ ├── usb_hid_keyboard.rs │ │ ├── usb_webusb.rs │ │ ├── watchdog.rs │ │ └── zerocopy.rs │ ├── std/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── bin/ │ │ │ │ ├── net.rs │ │ │ │ ├── net_dns.rs │ │ │ │ ├── net_ppp.rs │ │ │ │ ├── net_udp.rs │ │ │ │ ├── serial.rs │ │ │ │ ├── tcp_accept.rs │ │ │ │ ├── tick.rs │ │ │ │ └── tick_cancel.rs │ │ │ └── serial_port.rs │ │ └── tap.sh │ ├── stm32c0/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_ring_buffered_timer.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ └── rtc.rs │ ├── stm32f0/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc-watchdog.rs │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button_controlled_blink.rs │ │ ├── button_exti.rs │ │ ├── hello.rs │ │ ├── i2c_master.rs │ │ ├── i2c_slave_async.rs │ │ ├── i2c_slave_blocking.rs │ │ ├── multiprio.rs │ │ └── wdg.rs │ ├── stm32f072/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── i2c_loopback_test_async.rs │ ├── stm32f1/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── can.rs │ │ ├── hello.rs │ │ ├── input_capture.rs │ │ ├── pwm_input.rs │ │ └── usb_serial.rs │ ├── stm32f105/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── usb_serial.rs │ ├── stm32f107/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── eth.rs │ ├── stm32f2/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── pll.rs │ ├── stm32f3/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_events.rs │ │ ├── button_exti.rs │ │ ├── flash.rs │ │ ├── hello.rs │ │ ├── multiprio.rs │ │ ├── spi_dma.rs │ │ ├── tsc_blocking.rs │ │ ├── tsc_multipin.rs │ │ ├── usart_dma.rs │ │ └── usb_serial.rs │ ├── stm32f334/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── button.rs │ │ ├── hello.rs │ │ ├── hrtim.rs │ │ ├── opamp.rs │ │ └── pwm.rs │ ├── stm32f4/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── can.rs │ │ ├── dac.rs │ │ ├── eth.rs │ │ ├── eth_compliance_test.rs │ │ ├── eth_w5500.rs │ │ ├── flash.rs │ │ ├── flash_async.rs │ │ ├── hello.rs │ │ ├── i2c.rs │ │ ├── i2c_async.rs │ │ ├── i2c_comparison.rs │ │ ├── i2c_master_test_blocking.rs │ │ ├── i2c_slave_async.rs │ │ ├── i2c_slave_blocking.rs │ │ ├── i2s_dma.rs │ │ ├── input_capture.rs │ │ ├── mco.rs │ │ ├── multiprio.rs │ │ ├── pwm.rs │ │ ├── pwm_complementary.rs │ │ ├── pwm_input.rs │ │ ├── rtc.rs │ │ ├── sdmmc.rs │ │ ├── spi.rs │ │ ├── spi_dma.rs │ │ ├── usart.rs │ │ ├── usart_buffered.rs │ │ ├── usart_dma.rs │ │ ├── usb_ethernet.rs │ │ ├── usb_hid_keyboard.rs │ │ ├── usb_hid_mouse.rs │ │ ├── usb_raw.rs │ │ ├── usb_serial.rs │ │ ├── usb_uac_speaker.rs │ │ ├── wdt.rs │ │ ├── ws2812_pwm.rs │ │ └── ws2812_spi.rs │ ├── stm32f401/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── i2c_loopback_test_async.rs │ ├── stm32f469/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── dsi_bsp.rs │ ├── stm32f7/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── can.rs │ │ ├── cryp.rs │ │ ├── eth.rs │ │ ├── flash.rs │ │ ├── hash.rs │ │ ├── hello.rs │ │ ├── pwm.rs │ │ ├── pwm_ringbuffer.rs │ │ ├── qspi.rs │ │ ├── sdmmc.rs │ │ ├── usart_dma.rs │ │ └── usb_serial.rs │ ├── stm32g0/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── adc_oversampling.rs │ │ ├── adc_ring_buffered_timer.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── flash.rs │ │ ├── flash_async.rs │ │ ├── hf_timer.rs │ │ ├── i2c_async.rs │ │ ├── input_capture.rs │ │ ├── onewire_ds18b20.rs │ │ ├── pwm_complementary.rs │ │ ├── pwm_input.rs │ │ ├── rtc.rs │ │ ├── spi_neopixel.rs │ │ ├── usart.rs │ │ ├── usart_buffered.rs │ │ ├── usart_dma.rs │ │ └── usb_serial.rs │ ├── stm32g4/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_differential.rs │ │ ├── adc_dma.rs │ │ ├── adc_injected_and_regular.rs │ │ ├── adc_oversampling.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── can.rs │ │ ├── i2c_slave.rs │ │ ├── i2s.rs │ │ ├── pll.rs │ │ ├── pwm.rs │ │ ├── usb_c_pd.rs │ │ └── usb_serial.rs │ ├── stm32g474/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── comp.rs │ │ ├── flash_async.rs │ │ ├── hrtim.rs │ │ ├── hrtim_master.rs │ │ └── pwm_input_async.rs │ ├── stm32h5/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── backup_sram.rs │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── can.rs │ │ ├── cordic.rs │ │ ├── dts.rs │ │ ├── eth.rs │ │ ├── i2c.rs │ │ ├── mco.rs │ │ ├── rng.rs │ │ ├── sai.rs │ │ ├── usart.rs │ │ ├── usart_dma.rs │ │ ├── usart_split.rs │ │ ├── usb_c_pd.rs │ │ ├── usb_serial.rs │ │ ├── usb_uac_speaker.rs │ │ └── wifi_scan.rs │ ├── stm32h7/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── camera.rs │ │ ├── can.rs │ │ ├── dac.rs │ │ ├── dac_dma.rs │ │ ├── eth.rs │ │ ├── eth_client.rs │ │ ├── eth_client_mii.rs │ │ ├── flash.rs │ │ ├── flash_async.rs │ │ ├── fmc.rs │ │ ├── i2c.rs │ │ ├── i2c_shared.rs │ │ ├── low_level_timer_api.rs │ │ ├── mco.rs │ │ ├── multiprio.rs │ │ ├── pwm.rs │ │ ├── qspi_mdma.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── sai.rs │ │ ├── sdmmc.rs │ │ ├── signal.rs │ │ ├── spi.rs │ │ ├── spi_bdma.rs │ │ ├── spi_dma.rs │ │ ├── usart.rs │ │ ├── usart_dma.rs │ │ ├── usart_split.rs │ │ ├── usb_serial.rs │ │ └── wdg.rs │ ├── stm32h723/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── spdifrx.rs │ ├── stm32h735/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── ltdc.rs │ ├── stm32h742/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── qspi.rs │ ├── stm32h755cm4/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── intercore.rs │ ├── stm32h755cm7/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── intercore.rs │ ├── stm32h7b0/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── ospi_memory_mapped.rs │ ├── stm32h7rs/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── can.rs │ │ ├── eth.rs │ │ ├── i2c.rs │ │ ├── mco.rs │ │ ├── multiprio.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── signal.rs │ │ ├── spi.rs │ │ ├── spi_dma.rs │ │ ├── usart.rs │ │ ├── usart_dma.rs │ │ ├── usart_split.rs │ │ ├── usb_serial.rs │ │ └── xspi_memory_mapped.rs │ ├── stm32l0/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── dds.rs │ │ ├── eeprom.rs │ │ ├── flash.rs │ │ ├── raw_spawn.rs │ │ ├── spi.rs │ │ ├── tsc_async.rs │ │ ├── tsc_blocking.rs │ │ ├── tsc_multipin.rs │ │ ├── usart_dma.rs │ │ ├── usart_irq.rs │ │ └── usb_serial.rs │ ├── stm32l1/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── eeprom.rs │ │ ├── flash.rs │ │ ├── spi.rs │ │ ├── usart.rs │ │ └── usb_serial.rs │ ├── stm32l4/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_dma.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── can.rs │ │ ├── dac.rs │ │ ├── dac_dma.rs │ │ ├── flash_async.rs │ │ ├── i2c.rs │ │ ├── i2c_blocking_async.rs │ │ ├── i2c_dma.rs │ │ ├── mco.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── spe_adin1110_http_server.rs │ │ ├── spi.rs │ │ ├── spi_blocking_async.rs │ │ ├── spi_dma.rs │ │ ├── tsc_async.rs │ │ ├── tsc_blocking.rs │ │ ├── tsc_multipin.rs │ │ ├── usart.rs │ │ ├── usart_dma.rs │ │ └── usb_serial.rs │ ├── stm32l4-rtic/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── blinky_timer_interrupt.rs │ │ └── button_exti.rs │ ├── stm32l432/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── qspi_mmap.rs │ ├── stm32l5/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── button_exti.rs │ │ ├── rng.rs │ │ ├── usb_ethernet.rs │ │ ├── usb_hid_mouse.rs │ │ └── usb_serial.rs │ ├── stm32l5-lp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ └── stop.rs │ ├── stm32n6/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── crc.rs │ │ ├── hash.rs │ │ ├── xspi_flash.rs │ │ └── xspi_psram.rs │ ├── stm32u0/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── crc.rs │ │ ├── dac.rs │ │ ├── flash.rs │ │ ├── i2c.rs │ │ ├── lcd.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── spi.rs │ │ ├── usart.rs │ │ ├── usb_serial.rs │ │ └── wdt.rs │ ├── stm32u3/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── mco.rs │ │ ├── rtc.rs │ │ └── usb_serial.rs │ ├── stm32u5/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── boot.rs │ │ ├── flash.rs │ │ ├── hspi_memory_mapped.rs │ │ ├── i2c.rs │ │ ├── ltdc.rs │ │ ├── rng.rs │ │ ├── tsc.rs │ │ ├── usb_hs_serial.rs │ │ └── usb_serial.rs │ ├── stm32wb/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── eddystone_beacon.rs │ │ ├── fus_update.rs │ │ ├── gatt_server.rs │ │ ├── mac_ffd.rs │ │ ├── mac_ffd_net.rs │ │ ├── mac_rfd.rs │ │ ├── tl_mbox.rs │ │ ├── tl_mbox_ble.rs │ │ └── tl_mbox_mac.rs │ ├── stm32wba/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_ring_buffered.rs │ │ ├── aes_cbc.rs │ │ ├── aes_ccm.rs │ │ ├── aes_ctr.rs │ │ ├── aes_ecb.rs │ │ ├── aes_gcm.rs │ │ ├── aes_gmac.rs │ │ ├── ble_advertiser.rs │ │ ├── ble_central.rs │ │ ├── ble_gatt_server.rs │ │ ├── ble_peripheral_connect.rs │ │ ├── ble_scanner.rs │ │ ├── ble_secure.rs │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── device_info.rs │ │ ├── flash.rs │ │ ├── mac_ffd.rs │ │ ├── pka_ecdh.rs │ │ ├── pka_ecdsa_sign.rs │ │ ├── pka_ecdsa_verify.rs │ │ ├── pka_rsa.rs │ │ ├── pka_rsa_crt.rs │ │ ├── pka_rsa_keygen.rs │ │ ├── pwm.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── saes_ecb.rs │ │ └── saes_gcm.rs │ ├── stm32wba-lp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── button_exti.rs │ ├── stm32wba6/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README_PCM.md │ │ ├── build.rs │ │ ├── convert_wav.py │ │ ├── convert_wav.sh │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── adc_ring_buffered.rs │ │ ├── aes_cbc.rs │ │ ├── aes_ccm.rs │ │ ├── aes_ctr.rs │ │ ├── aes_ecb.rs │ │ ├── aes_gcm.rs │ │ ├── aes_gmac.rs │ │ ├── ble_advertiser.rs │ │ ├── ble_central.rs │ │ ├── ble_gatt_server.rs │ │ ├── ble_peripheral_connect.rs │ │ ├── ble_scanner.rs │ │ ├── ble_secure.rs │ │ ├── blinky.rs │ │ ├── button_exti.rs │ │ ├── comp.rs │ │ ├── comp_window.rs │ │ ├── device_info.rs │ │ ├── flash.rs │ │ ├── mac_ffd.rs │ │ ├── pka_ecdh.rs │ │ ├── pka_ecdsa_sign.rs │ │ ├── pka_ecdsa_verify.rs │ │ ├── pka_rsa.rs │ │ ├── pka_rsa_crt.rs │ │ ├── pka_rsa_keygen.rs │ │ ├── pwm.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── saes_ecb.rs │ │ ├── saes_gcm.rs │ │ ├── sdmmc_sai.rs │ │ ├── usb_hs_serial.rs │ │ └── wwdg.rs │ ├── stm32wba6-lp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── button_exti.rs │ ├── stm32wl/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── adc.rs │ │ ├── blinky.rs │ │ ├── button.rs │ │ ├── button_exti.rs │ │ ├── flash.rs │ │ ├── random.rs │ │ ├── rtc.rs │ │ └── uart_async.rs │ ├── stm32wl55cm0p/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── intercore.rs │ ├── stm32wl55cm0p-lp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── intercore.rs │ ├── stm32wl55cm4/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ └── intercore.rs │ ├── stm32wl55cm4-lp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── memory.x │ │ └── src/ │ │ └── bin/ │ │ ├── blinky.rs │ │ └── intercore.rs │ ├── stm32wle5-lp/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── src/ │ │ │ └── bin/ │ │ │ ├── adc.rs │ │ │ ├── blinky.rs │ │ │ ├── button_exti.rs │ │ │ └── i2c.rs │ │ └── stm32wle5.code-workspace │ └── wasm/ │ ├── Cargo.toml │ ├── README.md │ ├── index.html │ └── src/ │ └── lib.rs ├── fmtall.sh ├── release/ │ ├── bump-dependency.sh │ └── release.toml ├── rust-toolchain-nightly.toml ├── rust-toolchain.toml ├── rustfmt.toml └── tests/ ├── link_ram_cortex_m.x ├── mcxa2xx/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── memory.x │ └── src/ │ └── bin/ │ ├── adc.rs │ ├── adc_compare.rs │ ├── cdog.rs │ ├── crc.rs │ ├── ctimer_capture.rs │ ├── dma.rs │ ├── gpio.rs │ ├── i2c.rs │ ├── i3c.rs │ ├── lpuart.rs │ ├── pwm.rs │ ├── rtc_alarm.rs │ ├── trng.rs │ └── wwdt_interrupt.rs ├── mspm0/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ ├── memory_g3507.x │ ├── memory_g3519.x │ └── src/ │ └── bin/ │ ├── dma.rs │ ├── uart.rs │ └── uart_buffered.rs ├── nrf/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ ├── gen_test.py │ ├── memory-nrf51422.x │ ├── memory-nrf52832.x │ ├── memory-nrf52833.x │ ├── memory-nrf52840.x │ ├── memory-nrf5340.x │ ├── memory-nrf9160.x │ └── src/ │ ├── bin/ │ │ ├── buffered_uart.rs │ │ ├── buffered_uart_full.rs │ │ ├── buffered_uart_halves.rs │ │ ├── buffered_uart_spam.rs │ │ ├── ethernet_enc28j60_perf.rs │ │ ├── gpio.rs │ │ ├── gpiote.rs │ │ ├── spim.rs │ │ ├── timer.rs │ │ ├── uart_halves.rs │ │ ├── uart_split.rs │ │ └── wifi_esp_hosted_perf.rs │ └── common.rs ├── perf-client/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── perf-server/ │ ├── Cargo.toml │ ├── deploy.sh │ ├── perf-server.service │ └── src/ │ └── main.rs ├── riscv32/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ ├── link.x │ ├── memory.x │ └── src/ │ └── bin/ │ └── empty.rs ├── rp/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ ├── memory.x │ ├── readme.md │ └── src/ │ └── bin/ │ ├── adc.rs │ ├── aon_timer.rs │ ├── bootsel.rs │ ├── cyw43-perf.rs │ ├── dma_copy_async.rs │ ├── ethernet_w5100s_perf.rs │ ├── flash.rs │ ├── float.rs │ ├── gpio.rs │ ├── gpio_async.rs │ ├── gpio_multicore.rs │ ├── i2c.rs │ ├── multicore.rs │ ├── overclock.rs │ ├── pio_irq.rs │ ├── pio_multi_load.rs │ ├── pwm.rs │ ├── rtc.rs │ ├── spi.rs │ ├── spi_async.rs │ ├── spinlock_mutex_multicore.rs │ ├── timer.rs │ ├── uart.rs │ ├── uart_buffered.rs │ ├── uart_dma.rs │ └── uart_upgrade.rs ├── stm32/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ ├── gen_test.py │ └── src/ │ ├── bin/ │ │ ├── adc.rs │ │ ├── afio.rs │ │ ├── can.rs │ │ ├── cordic.rs │ │ ├── cryp.rs │ │ ├── dac.rs │ │ ├── dac_l1.rs │ │ ├── eeprom.rs │ │ ├── eth.rs │ │ ├── fdcan.rs │ │ ├── gpio.rs │ │ ├── hash.rs │ │ ├── hsem.rs │ │ ├── rng.rs │ │ ├── rtc.rs │ │ ├── sdmmc.rs │ │ ├── spi.rs │ │ ├── spi_dma.rs │ │ ├── stop.rs │ │ ├── timer.rs │ │ ├── ucpd.rs │ │ ├── usart.rs │ │ ├── usart_dma.rs │ │ ├── usart_rx_ringbuffered.rs │ │ ├── wpan_ble.rs │ │ └── wpan_mac.rs │ ├── can_common.rs │ └── common.rs └── utils/ ├── Cargo.toml └── src/ └── bin/ └── saturate_serial.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * 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 # Configure changelog files to use union merge strategy # This prevents merge conflicts by automatically combining changes from both branches CHANGELOG.md merge=union changelog.md merge=union CHANGELOG.txt merge=union changelog.txt merge=union *.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 ================================================ FILE: .github/ci/book.sh ================================================ #!/bin/bash ## on push branch=main ## priority -100 ## dedup dequeue ## cooldown 15m set -euxo pipefail make -C docs export KUBECONFIG=/ci/secrets/kubeconfig.yml POD=$(kubectl 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 ================================================ FILE: .github/ci/build-nightly.sh ================================================ #!/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 export PATH=$CARGO_HOME/bin:$PATH 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 cargo install --git https://github.com/embassy-rs/cargo-embassy-devtool --locked --rev 8f4cfa11324c582467c2aab161ef963ff7a2b884 ./ci-nightly.sh # Save lockfiles echo Saving lockfiles... find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ ================================================ FILE: .github/ci/build-xtensa.sh ================================================ #!/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 export PATH=$CARGO_HOME/bin:$PATH # 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 --locked espup install --toolchain-version 1.92.0.0 # 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 cargo install --git https://github.com/embassy-rs/cargo-embassy-devtool --locked --rev 8f4cfa11324c582467c2aab161ef963ff7a2b884 ./ci-xtensa.sh # Save lockfiles echo Saving lockfiles... find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ ================================================ FILE: .github/ci/build.sh ================================================ #!/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 export PATH=$CARGO_HOME/bin:$PATH 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 cargo install --git https://github.com/embassy-rs/cargo-embassy-devtool --locked --rev 8f4cfa11324c582467c2aab161ef963ff7a2b884 ./ci.sh # Save lockfiles echo Saving lockfiles... find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ ================================================ FILE: .github/ci/doc.sh ================================================ #!/bin/bash ## on push branch=main ## priority -100 ## dedup dequeue ## cooldown 15m set -euxo pipefail export RUSTUP_HOME=/ci/cache/rustup export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target export PATH=$CARGO_HOME/bin:$PATH mv rust-toolchain-nightly.toml rust-toolchain.toml cargo install --git https://github.com/embassy-rs/cargo-embassy-devtool --locked --rev 8f4cfa11324c582467c2aab161ef963ff7a2b884 cargo install --git https://github.com/embassy-rs/docserver --locked --rev 09805a79beef037d283192146e2b546cb1c0e931 cargo embassy-devtool doc -o webroot export KUBECONFIG=/ci/secrets/kubeconfig.yml POD=$(kubectl get po -l app=docserver -o jsonpath={.items[0].metadata.name}) kubectl cp webroot/crates $POD:/data kubectl cp webroot/static $POD:/data ================================================ FILE: .github/ci/janitor.sh ================================================ #!/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 export PATH=$CARGO_HOME/bin:$PATH cargo install --git https://github.com/embassy-rs/cargo-embassy-devtool --locked --rev 8f4cfa11324c582467c2aab161ef963ff7a2b884 cargo embassy-devtool check-crlf cargo embassy-devtool check-manifest ================================================ FILE: .github/ci/rustfmt.sh ================================================ #!/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 2024 ================================================ FILE: .github/ci/test-nightly.sh ================================================ #!/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 ================================================ FILE: .github/ci/test.sh ================================================ #!/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 --features metadata-name 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-utils/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,time-driver-any,exti,single-bank cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,time-driver-any,exti,dual-bank cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,time-driver-any,exti cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,time-driver-any,exti,single-bank cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,time-driver-any,exti,dual-bank cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml cargo test --manifest-path ./embassy-usb-dfu/Cargo.toml --features dfu ================================================ FILE: .github/workflows/matrix-bot-issues.yml ================================================ name: Matrix bot for issues on: issues: types: [opened] jobs: notify: if: github.repository == 'embassy-rs/embassy' runs-on: ubuntu-latest continue-on-error: true steps: - name: send message uses: actions/github-script@v8 with: script: | const action = context.payload.action; const title = context.payload.issue.title; const issueBody = context.payload.issue.body; const url = context.payload.issue.html_url; // Determine message prefix let prefix; let roomId; if (action === 'opened' && (title + ' ' + issueBody).toLowerCase().includes('esp32')) { // Nag danielb prefix = 'New Issue'; roomId = '!oENPKPHeKwiPfIVHER:matrix.org'; } else { return; // Unknown action, skip } // HTML escape the title const titleEscaped = title .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); const body = `${prefix}: ${title} - ${url}`; const formattedBody = `${prefix}: ${titleEscaped}`; const accessToken = process.env.MATRIX_ACCESS_TOKEN; const response = await fetch( `https://matrix.org/_matrix/client/r0/rooms/${roomId}/send/m.room.message`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify({ msgtype: 'm.text', format: 'org.matrix.custom.html', body: body, formatted_body: formattedBody }) } ); if (!response.ok) { throw new Error(`Matrix API request failed: ${response.status} ${response.statusText}`); } env: MATRIX_ACCESS_TOKEN: ${{ secrets.MATRIX_ACCESS_TOKEN }} ================================================ FILE: .github/workflows/matrix-bot.yml ================================================ name: Matrix bot on: pull_request_target: types: [opened, closed] jobs: notify: if: github.repository == 'embassy-rs/embassy' runs-on: ubuntu-latest continue-on-error: true steps: - name: send message uses: actions/github-script@v8 with: script: | const action = context.payload.action; const merged = context.payload.pull_request.merged; const title = context.payload.pull_request.title; const url = context.payload.pull_request.html_url; // Determine message prefix let prefix; if (action === 'opened') { prefix = 'New PR'; } else if (action === 'closed' && merged) { prefix = 'PR merged'; } else if (action === 'closed' && !merged) { prefix = 'PR closed without merging'; } else { return; // Unknown action, skip } // HTML escape the title const titleEscaped = title .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); const body = `${prefix}: ${title} - ${url}`; const formattedBody = `${prefix}: ${titleEscaped}`; const roomId = '!YoLPkieCYHGzdjUhOK:matrix.org'; const accessToken = process.env.MATRIX_ACCESS_TOKEN; const response = await fetch( `https://matrix.org/_matrix/client/r0/rooms/${roomId}/send/m.room.message`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify({ msgtype: 'm.text', format: 'org.matrix.custom.html', body: body, formatted_body: formattedBody }) } ); if (!response.ok) { throw new Error(`Matrix API request failed: ${response.status} ${response.statusText}`); } env: MATRIX_ACCESS_TOKEN: ${{ secrets.MATRIX_ACCESS_TOKEN }} ================================================ FILE: .gitignore ================================================ target target_ci target_ci_stable Cargo.lock third_party webroot /Cargo.toml out/ # editor artifacts .zed .neoconf.json *.vim **/.idea **/.nvim.lua **/.cargo !**/tests/**/.cargo !**/examples/**/.cargo ================================================ FILE: .helix/languages.toml ================================================ [language-server.rust-analyzer.config.cargo] allTargets = false noDefaultFeatures = true target = "thumbv8m.main-none-eabihf" features = ["stm32n657x0", "time-driver-any", "unstable-pac", "exti"] [language-server.rust-analyzer.config.check] allTargets = false noDefaultFeatures = true target = "thumbv8m.main-none-eabihf" features = ["stm32n657x0", "time-driver-any", "unstable-pac", "exti"] ================================================ FILE: .vscode/.gitignore ================================================ *.cortex-debug.*.json launch.json tasks.json *.cfg ================================================ FILE: .vscode/extensions.json ================================================ { // 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": [] } ================================================ FILE: .vscode/settings.json ================================================ { "[toml]": { "editor.formatOnSave": false }, "[markdown]": { "editor.formatOnSave": false }, "rust-analyzer.rustfmt.extraArgs": [ "+nightly" ], "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. "stm32f107rb", "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/mspm0c1104/Cargo.toml", // "examples/mspm0g3507/Cargo.toml", // "examples/mspm0g3519/Cargo.toml", // "examples/mspm0g5187/Cargo.toml", // "examples/mspm0l1306/Cargo.toml", // "examples/mspm0l2228/Cargo.toml", // "examples/nrf52840-rtic/Cargo.toml", // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", // "examples/mimxrt1011/Cargo.toml", // "examples/mimxrt1062-evk/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", ], } ================================================ FILE: LICENSE-APACHE ================================================ 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. ================================================ FILE: LICENSE-CC-BY-SA ================================================ 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. ================================================ FILE: LICENSE-MIT ================================================ 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. ================================================ FILE: NOTICE.md ================================================ # 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 ================================================ FILE: README.md ================================================ # 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](https://embassy.dev/book/index.html) - [API reference](https://docs.embassy.dev/) - [Website](https://embassy.dev/) - [Chat](https://matrix.to/#/#embassy-rs:matrix.org) ## 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](https://rust-lang.github.io/async-book/) allows for unprecedentedly easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is [faster and smaller than one!](https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown) ## Batteries included - **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy. - [embassy-stm32](https://docs.embassy.dev/embassy-stm32/), for all STM32 microcontroller families. - [embassy-nrf](https://docs.embassy.dev/embassy-nrf/), for the Nordic Semiconductor nRF52, nRF53, nRF54 and nRF91 series. - [embassy-rp](https://docs.embassy.dev/embassy-rp/), for the Raspberry Pi RP2040 and RP23xx microcontrollers. - [embassy-mspm0](https://docs.embassy.dev/embassy-mspm0/), for the Texas Instruments MSPM0 microcontrollers. - [embassy-mcxa](https://docs.embassy.dev/embassy-mcxa/), for NXP's MCX-A series of microcontrollers. - [esp-rs](https://github.com/esp-rs), for the Espressif Systems ESP32 series of chips. - Embassy HAL support for Espressif chips, as well as Async Wi-Fi, Bluetooth, and ESP-NOW, is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository. - [ch32-hal](https://github.com/ch32-rs/ch32-hal), for the WCH 32-bit RISC-V(CH32V) series of chips. - [mpfs-hal](https://github.com/AlexCharlton/mpfs-hal), for the Microchip PolarFire SoC. - [py32-hal](https://github.com/py32-rs/py32-hal), for the Puya Semiconductor PY32 series of microcontrollers. - **Time that Just Works** - No more messing with hardware timers. [embassy_time](https://docs.embassy.dev/embassy-time) provides Instant, Duration, and Timer types that are globally available and never overflow. - **Real-time ready** - Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities so that higher priority tasks preempt lower priority ones. See the [example](https://github.com/embassy-rs/embassy/blob/main/examples/nrf52840/src/bin/multiprio.rs). - **Low-power ready** - Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting. - **Networking** - The [embassy-net](https://docs.embassy.dev/embassy-net/) network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP, and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently. - **Bluetooth** - The [trouble](https://github.com/embassy-rs/trouble) crate provides a Bluetooth Low Energy 4.x and 5.x Host that runs on any microcontroller implementing the [bt-hci](https://github.com/embassy-rs/bt-hci) traits (currently `nRF52`, `nrf54`, `rp2040`, `rp23xx` and `esp32` and `serial` controllers are supported). - The [nrf-softdevice](https://github.com/embassy-rs/nrf-softdevice) crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. - The [embassy-stm32-wpan](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan) crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers. - **LoRa** - The [lora-rs](https://github.com/lora-rs/lora-rs) project provides an async LoRa and LoRaWAN stack that works well on Embassy. - **USB** - [embassy-usb](https://docs.embassy.dev/embassy-usb/) implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. - **Bootloader and DFU** - [embassy-boot](https://github.com/embassy-rs/embassy/tree/main/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::{Peri, Peripherals}; // Declare async tasks #[embassy_executor::task] async fn blink(pin: Peri<'static, 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.into()).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 and RP235x chips. * `examples/mcxa` run on the `FRDM-MCXA266` board. * `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://embassy-rs.github.io/embassy-book/embassy/dev/getting_started.html [2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples ================================================ FILE: RELEASE.md ================================================ # RELEASE.md This document outlines the process for releasing Embassy crates using `cargo-release` and the `release/bump-dependency.sh` script. When releasing a crate, keep in mind that you may need to recursively release dependencies as well. ## Prerequisites - Install [`cargo-release`](https://github.com/crate-ci/cargo-release): ```sh cargo binstall cargo-release ## Crate release Check if there are changes to the public API since the last release. If there is a breaking change, follow the process for creating a minor release. Otherewise, follow the process for creating a new patch release. Keep in mind that some crates may need the --features and --target flags passed to cargo release. For more information on that, look at the `Cargo.toml` files. ### Patch release (no breaking public API changes) ``` cd embassy-nrf/ cargo release patch --features time,defmt,unstable-pac,gpiote,time-driver-rtc1,nrf52840 --target thumbv7em-none-eabi # If dry-run is OK (no missing dependencies on crates.io) cargo release patch --execute --features time,defmt,unstable-pac,gpiote,time-driver-rtc1,nrf52840 --target thumbv7em-none-eabi ``` ### Minor release ``` # Bump versions in crate files ./release/bump-dependency.sh embassy-nrf 0.4.0 # Commit version bump git commit -am 'chore: update to `embassy-nrf` v0.4.0' # Release crate cd embassy-nrf/ cargo release minor --features time,defmt,unstable-pac,gpiote,time-driver-rtc1,nrf52840 --target thumbv7em-none-eabi # If dry-run is OK (no missing dependencies on crates.io) cargo release minor --execute --features time,defmt,unstable-pac,gpiote,time-driver-rtc1,nrf52840 --target thumbv7em-none-eabi ``` ## Push tags Push the git tags that `cargo release` created earlier: ``` git push --tags ``` ## Reference * [PR introducing release automation](https://github.com/embassy-rs/embassy/pull/4289) ================================================ FILE: ci-nightly.sh ================================================ #!/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 embassy-devtool build --group nightly ================================================ FILE: ci-xtensa.sh ================================================ #!/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 embassy-devtool build --group xtensa ================================================ FILE: ci.sh ================================================ #!/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 if ! command -v cargo-embassy-devtool &> /dev/null; then echo "cargo-embassy-devtool could not be found. Install it with the following command:" echo "" echo " cargo install --git https://github.com/embassy-rs/cargo-embassy-devtool --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 cargo embassy-devtool build # temporarily disabled, these boards are dead. rm -rf out/tests/stm32f103c8 rm -rf out/tests/nrf52840-dk rm -rf out/tests/nrf52833-dk rm -rf out/tests/nrf5340-dk # disabled because these boards are not on the shelf rm -rf out/tests/mspm0g3507 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, flaky. rm out/tests/stm32f207zg/usart_rx_ringbuffered rm out/tests/stm32l152re/usart_rx_ringbuffered # doesn't work, gives "noise error", no idea why. usart_dma does pass. rm out/tests/stm32u5a5zj/usart # probe-rs error: "multi-core ram flash start not implemented yet" # As of 2025-02-17 these tests work when run from flash rm out/tests/pimoroni-pico-plus-2/multicore rm out/tests/pimoroni-pico-plus-2/gpio_multicore rm out/tests/pimoroni-pico-plus-2/spinlock_mutex_multicore # Doesn't work when run from ram on the 2350 rm out/tests/pimoroni-pico-plus-2/flash # This test passes locally but fails on the HIL, no idea why rm out/tests/pimoroni-pico-plus-2/i2c # The pico2 plus doesn't have the adcs hooked up like the picoW does. rm out/tests/pimoroni-pico-plus-2/adc # temporarily disabled rm out/tests/pimoroni-pico-plus-2/pwm rm out/tests/frdm-mcx-a266/trng # flaky rm out/tests/rpi-pico/pwm rm out/tests/rpi-pico/cyw43-perf rm out/tests/rpi-pico/uart_buffered rm out/tests/rpi-pico/spi_async rm out/tests/stm32h563zi/usart_dma # tests are implemented but the HIL test farm doesn't actually have these boards, yet rm -rf out/tests/stm32c071rb rm -rf out/tests/stm32f100rd rm -rf out/tests/stm32f107vc if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests exit fi teleprobe client run -r out/tests ================================================ FILE: cyw43/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.7.0 - 2026-03-10 - Reset WPA security before creating secure AP - Add `get_rssi()` to `Controller` - Implement link state monitoring - Always unsubscribe to cyw43 events (fix event leak) - Add SDIO support for STM32 - Allow specifying nvram, get LWB+ working - Improve join handling with better error reporting - Replace `num_enum` dependency with internal macro - Bump bt-hci to 0.8.0. - Update to embedded-io 0.7 - Update embassy-sync to 0.8.0 - Update embassy-net-driver-channel to 0.4.0 ## 0.6.0 - 2025-11-27 - Updated documentation for Control::join() #4678 - Bump bt-hci to 0.6.0. - Add error handling to HCI transport implementation. - Reset WPA security on AP creation #4709 ## 0.5.0 - 2025-08-28 - bump bt-hci to 0.4.0 ## 0.4.1 - 2025-08-26 (yanked) - bump bt-hci to 0.4.0 ## 0.4.0 - 2025-07-15 - bump embassy-sync to 0.7.0 - bump bt-hci to 0.3.0 - make State::new const fn ## 0.3.0 - 2025-01-05 - Update `embassy-time` to 0.4.0 - Add Bluetooth support. - Add WPA3 support. - Expand wifi security configuration options. ## 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 ================================================ FILE: cyw43/Cargo.toml ================================================ [package] name = "cyw43" version = "0.7.0" edition = "2024" 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", "embassy-time/defmt", "bt-hci?/defmt", "embedded-io-async?/defmt"] log = ["dep:log", "dep:derive_more", "derive_more/display"] 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.5.1", path = "../embassy-time"} embassy-sync = { version = "0.8.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.2", path = "../embassy-futures"} embassy-net-driver-channel = { version = "0.4.0", path = "../embassy-net-driver-channel"} defmt = { version = "1.0.1", optional = true } log = { version = "0.4.17", optional = true } derive_more = { version = "2.1.1", optional = true, default-features =false } 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" } embedded-hal-async = { version = "1.0" } heapless = "0.9" aligned = "0.4.3" # Bluetooth deps embedded-io-async = { version = "0.7.0", optional = true } bt-hci = { version = "0.8", optional = true } [package.metadata.embassy] build = [ {target = "thumbv6m-none-eabi", features = []}, {target = "thumbv6m-none-eabi", features = ["log"]}, {target = "thumbv6m-none-eabi", features = ["defmt"]}, {target = "thumbv6m-none-eabi", features = ["firmware-logs", "log"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "firmware-logs"]}, {target = "thumbv6m-none-eabi", features = ["bluetooth", "firmware-logs", "log"]}, {target = "thumbv6m-none-eabi", features = ["bluetooth", "defmt", "firmware-logs"]}, ] [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"] ================================================ FILE: cyw43/README.md ================================================ # cyw43 Rust driver for the CYW43439 wifi+bluetooth chip. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). Works on the following boards: - Raspberry Pi Pico W (RP2040) - Raspberry Pi Pico 2 W (RP2350A) - Pimoroni Pico Plus 2 W (RP2350B) - Any board with Raspberry Pi RM2 radio module. - Any board with the CYW43439 chip, and possibly others if the protocol is similar enough. ## Features Working: - WiFi support - 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, no busy polling. - GPIO support (for LED on the Pico W). - Bluetooth support - Bluetooth Classic + LE HCI commands. - Concurrent operation with WiFi. - Implements the [bt-hci](https://crates.io/crates/bt-hci) controller traits. - Works with the [TrouBLE](https://github.com/embassy-rs/trouble) bluetooth LE stack. Check its repo for examples using `cyw43`. ## Running the WiFi 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. ================================================ FILE: cyw43/src/bluetooth.rs ================================================ use core::cell::RefCell; use core::future::Future; use core::mem::MaybeUninit; use bt_hci::transport::WithIndicator; use bt_hci::{ControllerToHostPacket, FromHciBytes, FromHciBytesError, 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_io_async::ErrorKind; use crate::consts::*; use crate::runner::Bus; pub use crate::spi::SpiBusCyw43; use crate::util::round_up; use crate::{CHIP, util}; 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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 impl 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; } } } } /// HCI transport error. #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug)] pub enum Error { /// I/O error. Io(ErrorKind), } impl From for Error { fn from(e: FromHciBytesError) -> Self { match e { FromHciBytesError::InvalidSize => Error::Io(ErrorKind::InvalidInput), FromHciBytesError::InvalidValue => Error::Io(ErrorKind::InvalidData), } } } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt(self, f) } } impl core::error::Error for Error {} impl<'d> embedded_io_async::ErrorType for BtDriver<'d> { type Error = Error; } impl embedded_io_async::Error for Error { fn kind(&self) -> ErrorKind { match self { Self::Io(e) => *e, } } } 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])?; let (pkt, _) = ControllerToHostPacket::from_hci_bytes_with_kind(kind, &rx[1..n])?; Ok(pkt) } } /// 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) .map_err(|_| Error::Io(ErrorKind::Other))?; buf.len = buf_len - slice.len(); ch.send_done(); Ok(()) } } } ================================================ FILE: cyw43/src/consts.rs ================================================ #![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 SPI_STATUS_REGISTER: u32 = 0x00000008; pub(crate) const INITIAL_READ: u32 = 0x04; 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 SDIO_FUNCTION_INT_MASK: u32 = 0x34; pub(crate) const SDIO_TO_SB_MAILBOX: u32 = 0x40; pub(crate) const SDIO_TO_SB_MAILBOX_DATA: u32 = 0x48; pub(crate) const SDIO_TO_HOST_MAILBOX_DATA: u32 = 0x4C; pub(crate) const SDIO_SLEEP_CSR: u32 = 0x1001F; pub(crate) const SBSDIO_SLPCSR_KEEP_WL_KS: u32 = 1 << 0; pub(crate) const SMB_DEV_INT: u32 = 1 << 3; pub(crate) const SMB_INT_ACK: u32 = 1 << 1; pub(crate) const I_HMB_HOST_INT: u32 = 1 << 7; pub(crate) const I_HMB_DATA_FWHALT: u32 = 0x0010; pub(crate) const HOSTINTMASK: u32 = 0x000000F0; pub(crate) const BUS_SD_DATA_WIDTH_MASK: u32 = 0x03; pub(crate) const BUS_SD_DATA_WIDTH_4BIT: u32 = 0x02; pub(crate) const SDIO_SPEED_EHS: u32 = 0x02; pub(crate) const SDIOD_CCCR_BRCM_CARDCAP_SECURE_MODE: u32 = 0x80; pub(crate) const SBSDIO_DEVICE_CTL: u32 = 0x10009; pub(crate) const SBSDIO_DEVCTL_ADDR_RST: u32 = 0x40; pub(crate) const SDIO_CORE_CHIPID_REG: u32 = 0x330; pub(crate) const SBSDIO_FUNC1_SBADDRLOW: u32 = 0x1000A; pub(crate) const SBSDIO_FUNC1_SBADDRMID: u32 = 0x1000B; pub(crate) const SBSDIO_FUNC1_SBADDRHIGH: u32 = 0x1000C; pub(crate) const SPI_F2_WATERMARK: u8 = 0x20; pub(crate) const SDIO_F2_WATERMARK: u8 = 0x08; 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; pub(crate) const BLOCK_BUFFER_SIZE: usize = 1024; // Active Low Power (ALP) clock constants pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; pub(crate) const BACKPLANE_FORCE_HW_CLKREQ_OFF: u8 = 0x20; pub(crate) const BACKPLANE_FORCE_ALP: u8 = 0x01; pub(crate) const BACKPLANE_FORCE_HT: u32 = 0x02; // 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; pub(crate) const SDIOD_CCCR_IOEN: u32 = 0x02; pub(crate) const SDIOD_CCCR_IORDY: u32 = 0x03; pub(crate) const SDIOD_CCCR_INTEN: u32 = 0x04; pub(crate) const SDIOD_CCCR_BICTRL: u32 = 0x07; pub(crate) const SDIOD_CCCR_BLKSIZE_0: u32 = 0x10; pub(crate) const SDIOD_CCCR_SPEED_CONTROL: u32 = 0x13; pub(crate) const SDIOD_CCCR_BRCM_CARDCAP: u32 = 0xf0; pub(crate) const SDIOD_SEP_INT_CTL: u32 = 0xf2; pub(crate) const SDIOD_CCCR_F1BLKSIZE_0: u32 = 0x110; pub(crate) const SDIOD_CCCR_F2BLKSIZE_0: u32 = 0x210; pub(crate) const SDIOD_CCCR_F2BLKSIZE_1: u32 = 0x211; pub(crate) const INTR_CTL_MASTER_EN: u32 = 0x01; pub(crate) const INTR_CTL_FUNC1_EN: u32 = 0x02; pub(crate) const INTR_CTL_FUNC2_EN: u32 = 0x04; pub(crate) const SDIO_FUNC_ENABLE_1: u32 = 0x02; pub(crate) const SDIO_FUNC_ENABLE_2: u32 = 0x04; pub(crate) const SDIO_FUNC_READY_1: u32 = 0x02; pub(crate) const SDIO_FUNC_READY_2: u32 = 0x04; pub(crate) const SDIO_64B_BLOCK: u32 = 64; pub(crate) const SDIO_CHIP_CLOCK_CSR: u32 = 0x1000e; pub(crate) const SDIO_PULL_UP: u32 = 0x1000f; // SDIOD_SEP_INT_CTL bits pub(crate) const SEP_INTR_CTL_MASK: u32 = 0x01; // out-of-band interrupt mask pub(crate) const SEP_INTR_CTL_EN: u32 = 0x02; // out-of-band interrupt output enable pub(crate) const SEP_INTR_CTL_POL: u32 = 0x04; // out-of-band interrupt polarity pub(crate) const CHIPCOMMON_BASE_ADDRESS: u32 = 0x18000000; pub(crate) const SDIO_BASE_ADDRESS: u32 = 0x18002000; // 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, } crate::util::enum_from_u8! { #[allow(non_camel_case_types)] #[derive(Copy, Clone, PartialEq)] enum EStatus { #[default] Unknown = 0xFF, /// 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; ================================================ FILE: cyw43/src/control.rs ================================================ use core::cmp::{max, min}; use core::iter::zip; use core::sync::atomic::AtomicBool; use core::sync::atomic::Ordering::Relaxed; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::HardwareAddress; 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::{PowerManagementMode, countries, events}; /// Join errors. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum JoinError { /// Network not found. NetworkNotFound, /// Failure to join network. Contains the status code from the SET_SSID event. JoinFailure(u8), /// Authentication failure for a secure network. AuthenticationFailure, } /// 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, secure_network: &'a AtomicBool, } /// WiFi scan type. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ScanType { /// Active scan: the station actively transmits probes that make APs respond. /// Faster, but uses more power. Active, /// Passive scan: the station doesn't transmit any probes, just listens for beacons. /// Slower, but uses less power. 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, secure_network: &'a AtomicBool, ) -> Self { Self { state_ch, events: event_sub, ioctl_state, secure_network, } } 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 a network with the provided SSID using the specified options. pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), JoinError> { 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()); let secure_network = options.auth != JoinAuth::Open; self.secure_network.store(secure_network, Relaxed); self.wait_for_join(i, secure_network).await } async fn wait_for_join(&mut self, i: SsidInfo, secure_network: bool) -> Result<(), JoinError> { struct UnsubscribeOnDrop<'a>(&'a Events); impl Drop for UnsubscribeOnDrop<'_> { fn drop(&mut self) { self.0.mask.disable_all(); } } let _uod = UnsubscribeOnDrop(&self.events); self.events.mask.enable(&[Event::SET_SSID, Event::AUTH, Event::PSK_SUP]); 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 on an open network, we wait for a SET_SSID event with status SUCCESS // For secured networks, we wait for a PSK_SUP event with status 6 "UNSOLICITED" let result = loop { let msg = subscriber.next_message_pure().await; let status = EStatus::from(msg.header.status as u8); match (msg.header.event_type, status, secure_network) { // Join operation ends with SET_SSID event for open networks (Event::SET_SSID, EStatus::SUCCESS, false) => break Ok(()), (Event::SET_SSID, EStatus::NO_NETWORKS, _) => break Err(JoinError::NetworkNotFound), (Event::SET_SSID, status, _) if status != EStatus::SUCCESS => { break Err(JoinError::JoinFailure(status as u8)); } // Ignore PSK_SUP "ABORT" which is sometimes sent before successful join (Event::PSK_SUP, EStatus::ABORT, true) => {} // Event PSK_SUP with status 6 "UNSOLICITED" indicates success for secure networks (Event::PSK_SUP, EStatus::UNSOLICITED, true) => break Ok(()), // Events indicating authentication failure, possibly due to incorrect password (Event::PSK_SUP, _, true) | (Event::AUTH, EStatus::FAIL, true) => { break Err(JoinError::AuthenticationFailure); } _ => {} }; }; match result { Ok(()) => debug!("JOINED"), Err(JoinError::JoinFailure(status)) => debug!("JOIN failed: status={}", status), Err(JoinError::NetworkNotFound) => debug!("JOIN failed: network not found"), Err(JoinError::AuthenticationFailure) => debug!("JOIN failed: authentication failure"), }; result } /// 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; // Disable authentication self.ioctl_set_u32(Ioctl::SetAuth, 0, AUTH_OPEN).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_multicast_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 } /// Retrieve the latest RSSI value pub async fn get_rssi(&mut self) -> i32 { let mut rssi_buf = [0u8; 4]; let n = self.ioctl(IoctlType::Get, Ioctl::GetRssi, 0, &mut rssi_buf).await; assert_eq!(n, 4); i32::from_ne_bytes(rssi_buf) } 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(); } } ================================================ FILE: cyw43/src/countries.rs ================================================ #![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 }; ================================================ FILE: cyw43/src/events.rs ================================================ #![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; crate::util::enum_from_u8! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum Event { #[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 const fn new() -> Self { Self { queue: EventQueue::new(), mask: SharedEventMask::new(), } } } #[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, } } } struct EventMask { mask: [u32; Self::WORD_COUNT], } impl EventMask { const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; const fn new() -> Self { Self { mask: [0; Self::WORD_COUNT], } } 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 } } pub struct SharedEventMask { mask: RefCell, } impl SharedEventMask { pub const fn new() -> Self { Self { mask: RefCell::new(EventMask::new()), } } 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) } } impl Default for SharedEventMask { fn default() -> Self { Self::new() } } ================================================ FILE: cyw43/src/fmt.rs ================================================ #![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) } } ================================================ FILE: cyw43/src/ioctl.rs ================================================ use core::cell::{Cell, RefCell}; use core::future::{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 Wakers { const fn new() -> Self { Self { control: WakerRegistration::new(), runner: WakerRegistration::new(), } } } pub struct IoctlState { state: Cell, wakers: RefCell, } impl IoctlState { pub const fn new() -> Self { Self { state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), wakers: RefCell::new(Wakers::new()), } } 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 fn wait_complete(&self) -> impl Future + '_ { poll_fn(|cx| { if let IoctlStateInner::Done { resp_len } = self.state.get() { Poll::Ready(resp_len) } else { self.register_control(cx.waker()); Poll::Pending } }) } pub fn wait_pending(&self) -> impl Future + '_ { poll_fn(|cx| { if let IoctlStateInner::Pending(pending) = self.state.get() { self.state.set(IoctlStateInner::Sent { buf: pending.buf }); Poll::Ready(pending) } else { self.register_runner(cx.waker()); Poll::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"); } } } ================================================ FILE: cyw43/src/lib.rs ================================================ #![no_std] #![no_main] #![allow(async_fn_in_trait)] #![allow(unsafe_op_in_unsafe_fn)] #![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 consts; mod control; mod countries; mod events; mod ioctl; mod runner; mod sdio; mod spi; mod structs; mod util; use core::sync::atomic::AtomicBool; pub use aligned::{A4, Aligned}; use embassy_net_driver_channel as ch; use embedded_hal_1::digital::OutputPin; use events::Events; use ioctl::IoctlState; pub use crate::control::{ AddMulticastAddressError, Control, JoinAuth, JoinError, JoinOptions, ScanOptions, ScanType, Scanner, }; pub use crate::runner::Runner; pub use crate::sdio::{SdioBus, SdioBusCyw43}; pub use crate::spi::{SpiBus, SpiBusCyw43}; 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, secure_network: AtomicBool, } impl State { /// Create new driver state holder. pub const fn new() -> Self { Self { ioctl_state: IoctlState::new(), net: NetState { ch: ch::State::new(), events: Events::new(), secure_network: AtomicBool::new(false), }, #[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: &Aligned, nvram: &Aligned, ) -> (NetDriver<'a>, Control<'a>, Runner<'a, SpiBus>) 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, SpiBus::new(pwr, spi), &state.ioctl_state, &state.net.events, &state.net.secure_network, #[cfg(feature = "bluetooth")] None, ); runner.init(firmware, nvram, None).await.unwrap(); let control = Control::new( state_ch, &state.net.events, &state.ioctl_state, &state.net.secure_network, ); (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. pub async fn new_sdio<'a, SDIO>( state: &'a mut State, sdio: SDIO, firmware: &Aligned, nvram: &Aligned, ) -> (NetDriver<'a>, Control<'a>, Runner<'a, SdioBus>) where SDIO: SdioBusCyw43<64>, { 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, SdioBus::new(sdio), &state.ioctl_state, &state.net.events, &state.net.secure_network, #[cfg(feature = "bluetooth")] None, ); runner.init(firmware, nvram, None).await.unwrap(); let control = Control::new( state_ch, &state.net.events, &state.ioctl_state, &state.net.secure_network, ); (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: &Aligned, bluetooth_firmware: &Aligned, nvram: &Aligned, ) -> ( NetDriver<'a>, bluetooth::BtDriver<'a>, Control<'a>, Runner<'a, SpiBus>, ) 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, SpiBus::new(pwr, spi), &state.ioctl_state, &state.net.events, &state.net.secure_network, #[cfg(feature = "bluetooth")] Some(bt_runner), ); runner .init(wifi_firmware, nvram, Some(bluetooth_firmware)) .await .unwrap(); let control = Control::new( state_ch, &state.net.events, &state.ioctl_state, &state.net.secure_network, ); (device, bt_driver, control, runner) } /// Include bytes aligned to A4 in the binary #[macro_export] macro_rules! aligned_bytes { ($path:expr) => {{ { static BYTES: &cyw43::Aligned = &cyw43::Aligned(*include_bytes!($path)); BYTES } }}; } ================================================ FILE: cyw43/src/runner.rs ================================================ use core::sync::atomic::AtomicBool; use core::sync::atomic::Ordering::Relaxed; use aligned::{A4, Aligned}; use embassy_futures::select::{Either4, select4}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; use embassy_time::{Duration, Timer, block_for}; use crate::consts::*; use crate::events::{Event, Events, Status}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; pub use crate::spi::SpiBusCyw43; use crate::structs::*; use crate::util::{aligned_mut, aligned_ref, slice8_mut, slice16_mut, try_until}; use crate::{CHIP, Core, MTU, events}; #[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(), } } } pub(crate) enum BusType { Spi, Sdio, } pub(crate) trait SealedBus { const TYPE: BusType; async fn init(&mut self, bluetooth_enabled: bool); async fn wlan_read(&mut self, buf: &mut Aligned); async fn wlan_write(&mut self, buf: &Aligned); #[allow(unused)] async fn bp_read(&mut self, addr: u32, data: &mut [u8]); async fn bp_write(&mut self, addr: u32, data: &[u8]); async fn bp_read8(&mut self, addr: u32) -> u8; async fn bp_write8(&mut self, addr: u32, val: u8); async fn bp_read16(&mut self, addr: u32) -> u16; #[allow(unused)] async fn bp_write16(&mut self, addr: u32, val: u16); #[allow(unused)] async fn bp_read32(&mut self, addr: u32) -> u32; async fn bp_write32(&mut self, addr: u32, val: u32); async fn read8(&mut self, func: u32, addr: u32) -> u8; async fn write8(&mut self, func: u32, addr: u32, val: u8); async fn read16(&mut self, func: u32, addr: u32) -> u16; async fn write16(&mut self, func: u32, addr: u32, val: u16); async fn read32(&mut self, func: u32, addr: u32) -> u32; #[allow(unused)] async fn write32(&mut self, func: u32, addr: u32, val: u32); async fn wait_for_event(&mut self); } #[allow(private_bounds)] pub trait Bus: SealedBus {} impl Bus for T {} /// Driver communicating with the WiFi chip. pub struct Runner<'a, BUS> { 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, secure_network: &'a AtomicBool, join_ok: bool, key_exchange_ok: bool, auth_ok: bool, #[cfg(feature = "firmware-logs")] log: LogState, #[cfg(feature = "bluetooth")] pub(crate) bt: Option>, } impl<'a, BUS> Runner<'a, BUS> where BUS: Bus, { pub(crate) fn new( ch: ch::Runner<'a, MTU>, bus: BUS, ioctl_state: &'a IoctlState, events: &'a Events, secure_network: &'a AtomicBool, #[cfg(feature = "bluetooth")] bt: Option>, ) -> Self { Self { ch, bus, ioctl_state, ioctl_id: 0, sdpcm_seq: 0, sdpcm_seq_max: 1, events, secure_network, join_ok: false, key_exchange_ok: false, auth_ok: false, #[cfg(feature = "firmware-logs")] log: LogState::default(), #[cfg(feature = "bluetooth")] bt, } } pub(crate) async fn init( &mut self, wifi_fw: &Aligned, nvram: &Aligned, bt_fw: Option<&[u8]>, ) -> Result<(), ()> { self.bus.init(bt_fw.is_some()).await; // Init ALP (Active Low Power) clock debug!("init alp"); match BUS::TYPE { BusType::Spi => { self.bus .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) .await; // Not present in whd driver 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); } BusType::Sdio => { self.bus .write8( FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_FORCE_HW_CLKREQ_OFF | BACKPLANE_ALP_AVAIL_REQ | BACKPLANE_FORCE_ALP, ) .await; } } debug!("waiting for clock..."); if !try_until( async || self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL != 0, Duration::from_millis(100), ) .await { debug!("timeout while waiting for alp clock!"); return Err(()); } 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 = match BUS::TYPE { BusType::Spi => self.bus.bp_read16(CHIPCOMMON_BASE_ADDRESS).await, BusType::Sdio => { // Disable the extra sdio pull-ups // self.bus.write8(FUNC_BACKPLANE, SDIO_PULL_UP, 0).await; // Enable f1 and f2 self.bus .write8( FUNC_BUS, SDIOD_CCCR_IOEN, (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2) as u8, ) .await; // Enable out-of-band interrupt signal // whd_bus_sdio_init_oob_intr // Note: only GPIO0 using rising edge is currently supported self.bus .write8( FUNC_BUS, SDIOD_SEP_INT_CTL, (SEP_INTR_CTL_MASK | SEP_INTR_CTL_EN | SEP_INTR_CTL_POL) as u8, ) .await; // Enable f2 interrupt only self.bus .write8( FUNC_BUS, SDIOD_CCCR_INTEN, (INTR_CTL_MASTER_EN | INTR_CTL_FUNC2_EN) as u8, ) .await; self.bus.read8(FUNC_BUS, SDIOD_CCCR_IORDY).await; let reg = self.bus.read8(FUNC_BUS, SDIOD_CCCR_BRCM_CARDCAP).await; if reg & SDIOD_CCCR_BRCM_CARDCAP_SECURE_MODE as u8 != 0 { debug!("chip supports bootloader handshake"); let devctrl = self.bus.read8(FUNC_BACKPLANE, SBSDIO_DEVICE_CTL).await; self.bus .write8( FUNC_BACKPLANE, SBSDIO_DEVICE_CTL, devctrl | SBSDIO_DEVCTL_ADDR_RST as u8, ) .await; let addr_low = self.bus.read8(FUNC_BACKPLANE, SBSDIO_FUNC1_SBADDRLOW).await as u32; let addr_mid = self.bus.read8(FUNC_BACKPLANE, SBSDIO_FUNC1_SBADDRMID).await as u32; let addr_high = self.bus.read8(FUNC_BACKPLANE, SBSDIO_FUNC1_SBADDRHIGH).await as u32; let reg_addr = ((addr_low << 8) | (addr_mid << 16) | (addr_high << 24)) + SDIO_CORE_CHIPID_REG; self.bus.write8(FUNC_BACKPLANE, SBSDIO_DEVICE_CTL, devctrl).await; self.bus.bp_read16(reg_addr).await } else { self.bus.bp_read16(CHIPCOMMON_BASE_ADDRESS).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!("waiting 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; match BUS::TYPE { BusType::Sdio => { self.bus .bp_write8(CHIP.sdiod_core_base_address + SDIO_FUNCTION_INT_MASK, 2 | 1) .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, SDIO_F2_WATERMARK) .await; } BusType::Spi => { // 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..."); if !try_until( async || match BUS::TYPE { BusType::Sdio => self.bus.read8(FUNC_BUS, SDIOD_CCCR_IORDY).await as u32 & SDIO_FUNC_READY_2 != 0, BusType::Spi => self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY != 0, }, Duration::from_millis(1000), ) .await { debug!("timeout while waiting for function 2 to be ready"); return Err(()); } match BUS::TYPE { BusType::Sdio => { self.bus .write8(FUNC_BACKPLANE, SDIO_SLEEP_CSR, SBSDIO_SLPCSR_KEEP_WL_KS as u8) .await; self.bus .write8(FUNC_BACKPLANE, SDIO_SLEEP_CSR, SBSDIO_SLPCSR_KEEP_WL_KS as u8) .await; assert!(self.bus.read8(FUNC_BACKPLANE, SDIO_SLEEP_CSR).await & SBSDIO_SLPCSR_KEEP_WL_KS as u8 != 0); } BusType::Spi => {} } // 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"); Ok(()) } #[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: Aligned = Aligned([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: Aligned = Aligned([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: Aligned = Aligned([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(&aligned_ref(&buf)[..total_len]).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"); match BUS::TYPE { BusType::Sdio => { // whd_bus_sdio_poke_wlan self.bus .bp_write32(CHIP.sdiod_core_base_address + SDIO_TO_SB_MAILBOX, SMB_DEV_INT) .await; } BusType::Spi => {} } 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]) { match BUS::TYPE { BusType::Sdio => { // TODO: get irqs working self.check_status(buf).await; // whd_bus_sdio_packet_available_to_read let irq = self.bus.bp_read32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS).await; if irq & I_HMB_HOST_INT == 0 { // return; } let hmb_data = self .bus .bp_read32(CHIP.sdiod_core_base_address + SDIO_TO_HOST_MAILBOX_DATA) .await; self.bus .bp_write32(CHIP.sdiod_core_base_address + SDIO_TO_SB_MAILBOX, SMB_INT_ACK) .await; trace!("hmb ack"); if hmb_data & I_HMB_DATA_FWHALT != 0 { debug!("hmb data fault"); } // Clear irq must be done here to avoid a race if irq & HOSTINTMASK != 0 { trace!("clear irq"); self.bus .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS, irq & HOSTINTMASK) .await; } } BusType::Spi => { // 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 { match BUS::TYPE { BusType::Spi => { let status = self.bus.read32(FUNC_BUS, SPI_STATUS_REGISTER).await; 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(&mut aligned_mut(buf)[..len as usize]).await; trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); self.rx(&mut slice8_mut(buf)[..len as usize]); } else { break; } } BusType::Sdio => { self.bus.wlan_read(&mut aligned_mut(&mut buf[..1])).await; let (len, len_inv) = { let hwtag = slice16_mut(&mut buf[..1]); (hwtag[0], hwtag[1]) }; if (len | len_inv) == 0 || (len ^ len_inv) != 0xFFFF { trace!("hwtag mismatch (hwtag[0] = {}, hwtag[1] = {})", len, len_inv); break; } trace!("pkt ready..."); let len = len as usize; if len > INITIAL_READ as usize { self.bus .wlan_read(&mut aligned_mut(&mut buf[1..])[..len - INITIAL_READ as usize]) .await; } else { // TODO: investigate this condition trace!("no extra space required"); continue; } if len == SdpcmHeader::SIZE { let Some((sdpcm_header, _)) = SdpcmHeader::parse(slice8_mut(&mut buf[..3])) else { debug!("failed to parse sdpcm header"); break; }; self.update_credit(&sdpcm_header); } else if len > SdpcmHeader::SIZE { trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..len.min(48)])); self.rx(&mut slice8_mut(buf)[..len]); } } } } } 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 event_type = Event::from(event_packet.msg.event_type as u8); let status = EStatus::from(event_packet.msg.status as u8); debug!( "=== EVENT {:?}: {:?} {:02x}", event_type, event_packet.msg, Bytes(evt_data) ); let update_link_status = match ( event_type, status, event_packet.msg.flags, event_packet.msg.reason, event_packet.msg.auth_type, ) { // Events indicating that the link is down // Event LINK with flag 0 indicates link down. reason = 1: loss of signal (e.g. out of range), reason = 2: controlled network shutdown // Event AUTH with status FAIL, reason 16, and auth_type 3 is specific for WPA3 networks (Event::LINK, EStatus::SUCCESS, 0, ..) | (Event::DEAUTH, EStatus::SUCCESS, ..) | (Event::AUTH, EStatus::FAIL, _, 16, 3) => { self.auth_ok = false; self.join_ok = false; self.key_exchange_ok = false; true } // Update auth flag. Ignore unsolicited events. // When changing passwords on a WPA3 AP which we are already connected to, or we roam to, PSK_SUP events indicating // success are still sent. Only the AUTH events indicate failure and this flag helps cover that scenario (Event::AUTH, status, ..) if status != EStatus::UNSOLICITED => { self.auth_ok = status == EStatus::SUCCESS; debug!("auth_ok flag: {}", self.auth_ok as u8); false } // Successfully joined the network. Open or WPA3 networks are now fully connected - WPA1/2 networks additionally require a successful key exchange. (Event::JOIN, EStatus::SUCCESS, ..) => { self.join_ok = true; true } // Key exchange events (PSK_SUP) for secure networks // The status codes for PSK_SUP events seem to have different meanings from other event types // Successful key exchange, indicated by a PSK_SUP event with status 6 "UNSOLICITED" // Disregard if auth_ok is false, which can happen in WPA3 networks (Event::PSK_SUP, EStatus::UNSOLICITED, 0, 0, _) => { if self.auth_ok { self.key_exchange_ok = true; true } else { false } } // Ignore PSK_SUP events with reason 14 as they are often sent when the device roams from one AP to another (Event::PSK_SUP, _, _, 14, _) => false, // Other PSK_SUP events indicate key exchange errors (Event::PSK_SUP, ..) => { self.key_exchange_ok = false; true } _ => false, }; if update_link_status { let secure_network = self.secure_network.load(Relaxed); let link_state = if self.join_ok && (!secure_network || self.key_exchange_ok) { LinkState::Up } else { LinkState::Down }; self.ch.set_link_state(link_state); debug!( "link_ok: {}, secure_network: {}, auth_ok: {}, password_ok: {}, link_state {}", self.join_ok as u8, secure_network as u8, self.auth_ok as u8, self.key_exchange_ok as u8, link_state as u8 ); } if self.events.mask.is_enabled(event_type) { let status = event_packet.msg.status; let event_payload = match event_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, 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(&aligned_ref(buf)[..total_len]).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 } } ================================================ FILE: cyw43/src/sdio.rs ================================================ use core::slice; use aligned::{A4, Aligned}; use embassy_time::{Duration, Timer}; use crate::consts::*; use crate::runner::{BusType, SealedBus}; use crate::util::{aligned_mut, aligned_ref, slice32_mut, slice32_ref, try_until}; // macro_rules! ALIGN_UINT { // ($val:expr, $align:expr) => { // ((($val) + ($align) - 1) & !(($align) - 1)) // }; // } // // macro_rules! WRITE_BYTES_PAD { // ($len:expr) => { // ALIGN_UINT!($len, 64) // }; // } #[derive(Clone, Copy, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "log", derive(derive_more::Display))] enum Mode { Block, Byte, } #[derive(Clone, Copy, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "log", derive(derive_more::Display))] enum Word { U8, U16, U32, } const BLOCK_SIZE: usize = BACKPLANE_MAX_TRANSFER_SIZE; fn cmd53_arg(write: bool, func: u32, addr: u32, mode: Mode, len: usize) -> u32 { let (len, block_mode) = match mode { Mode::Block => (len / BLOCK_SIZE, 1u32), Mode::Byte => (len, 0u32), }; let op_code = 1; (write as u32) << 31 | func << 28 | block_mode << 27 | op_code << 26 | (addr & 0x1ffff) << 9 | len as u32 } /// 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 SdioBusCyw43 { /// The error type for the BlockDevice implementation. type Error: core::fmt::Debug; /// Set the bus to high speed 4-bit frequency fn set_bus_to_high_speed(&mut self, frequency: u32) -> Result<(), Self::Error>; /// Issue CMD52 async fn cmd52(&mut self, arg: u32) -> Result; /// Issue CMD53 in block read mode async fn cmd53_block_read(&mut self, arg: u32, blocks: &mut [Aligned]) -> Result<(), Self::Error>; /// Issue CMD53 in byte read mode async fn cmd53_byte_read(&mut self, arg: u32, buffer: &mut Aligned) -> Result<(), Self::Error>; /// Issue CMD53 in block write mode async fn cmd53_block_write(&mut self, arg: u32, blocks: &[Aligned]) -> Result<(), Self::Error>; /// Issue CMD53 in byte write mode async fn cmd53_byte_write(&mut self, arg: u32, buffer: &Aligned) -> Result<(), Self::Error>; /// 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) { Timer::after_millis(800).await; } } impl> SdioBusCyw43 for &mut T { type Error = T::Error; fn set_bus_to_high_speed(&mut self, frequency: u32) -> Result<(), Self::Error> { T::set_bus_to_high_speed(self, frequency) } async fn cmd52(&mut self, arg: u32) -> Result { T::cmd52(self, arg).await } async fn cmd53_block_read(&mut self, arg: u32, blocks: &mut [Aligned]) -> Result<(), Self::Error> { T::cmd53_block_read(self, arg, blocks).await } async fn cmd53_byte_read(&mut self, arg: u32, buffer: &mut Aligned) -> Result<(), Self::Error> { T::cmd53_byte_read(self, arg, buffer).await } async fn cmd53_block_write(&mut self, arg: u32, blocks: &[Aligned]) -> Result<(), Self::Error> { T::cmd53_block_write(self, arg, blocks).await } async fn cmd53_byte_write(&mut self, arg: u32, buffer: &Aligned) -> Result<(), Self::Error> { T::cmd53_byte_write(self, arg, buffer).await } async fn wait_for_event(&mut self) { T::wait_for_event(self).await } } /// Doc pub struct SdioBus { backplane_window: u32, sdio: SDIO, } impl SdioBus where SDIO: SdioBusCyw43, { pub(crate) fn new(sdio: SDIO) -> Self { Self { backplane_window: 0xAAAA_AAAA, sdio, } } async fn backplane_readn(&mut self, addr: u32, word: Word) -> u32 { trace!("backplane_readn addr = {:08x} len = {}", addr, word); self.backplane_set_window(addr).await; let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; if word == Word::U32 { bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; } let val = match word { Word::U8 => self.read8(FUNC_BACKPLANE, bus_addr).await as u32, Word::U16 => self.read16(FUNC_BACKPLANE, bus_addr).await as u32, Word::U32 => self.read32(FUNC_BACKPLANE, bus_addr).await, }; trace!("backplane_readn addr = {:08x} len = {} val = {:08x}", addr, word, val); self.backplane_set_window(CHIPCOMMON_BASE_ADDRESS).await; return val; } async fn backplane_writen(&mut self, addr: u32, val: u32, word: Word) { trace!("backplane_writen addr = {:08x} len = {} val = {:08x}", addr, word, val); self.backplane_set_window(addr).await; let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; if word == Word::U32 { bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; } let _ = match word { Word::U8 => self.write8(FUNC_BACKPLANE, bus_addr, val.try_into().unwrap()).await, Word::U16 => self.write16(FUNC_BACKPLANE, bus_addr, val.try_into().unwrap()).await, Word::U32 => self.write32(FUNC_BACKPLANE, bus_addr, val).await, }; self.backplane_set_window(CHIPCOMMON_BASE_ADDRESS).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; } async fn cmd52(&mut self, write: bool, func: u32, addr: u32, val: u8) -> u8 { let arg: u32 = (write as u32) << 31 | func << 28 | (addr & 0x1ffff) << 9 | (val as u32 & 0xff); let result = self.sdio.cmd52(arg).await.unwrap_or(u16::MAX) as u8; // trace!("cmd52: 0x{:08x} 0x{:08x}", arg, result); result } async fn cmd53_write(&mut self, func: u32, mut addr: u32, buf: &Aligned) { let byte_part = size_of_val(buf) % BLOCK_SIZE; let block_part = size_of_val(buf) - byte_part; if block_part > 0 { let buf = &buf[..block_part]; if self .sdio .cmd53_block_write(cmd53_arg(true, func, addr, Mode::Block, buf.len()), unsafe { slice::from_raw_parts(buf.as_ptr() as *mut _, size_of_val(buf) / BLOCK_SIZE) }) .await .is_err() { debug!("cmd53 block read failed"); } addr += block_part as u32; } if byte_part > 0 { let buf = &buf[block_part..]; if self .sdio .cmd53_byte_write(cmd53_arg(true, func, addr, Mode::Byte, buf.len()), buf) .await .is_err() { debug!("cmd53 byte read failed (size: {})", size_of_val(buf)); } } } async fn cmd53_read(&mut self, func: u32, mut addr: u32, buf: &mut Aligned) { let byte_part = size_of_val(buf) % BLOCK_SIZE; let block_part = size_of_val(buf) - byte_part; if block_part > 0 { let buf = &mut buf[..block_part]; if self .sdio .cmd53_block_read(cmd53_arg(false, func, addr, Mode::Block, buf.len()), unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut _, size_of_val(buf) / BLOCK_SIZE) }) .await .is_err() { debug!("cmd53 block read failed"); } addr += block_part as u32; } if byte_part > 0 { let buf = &mut buf[block_part..]; if self .sdio .cmd53_byte_read(cmd53_arg(false, func, addr, Mode::Byte, buf.len()), buf) .await .is_err() { debug!("cmd53 byte read failed (size: {})", size_of_val(buf)); } } } } impl SealedBus for SdioBus where SDIO: SdioBusCyw43<64>, { const TYPE: BusType = BusType::Sdio; async fn init(&mut self, _bluetooth_enabled: bool) { // whd_bus_sdio_init // set up backplane if !try_until( async || { self.write8(FUNC_BUS, SDIOD_CCCR_IOEN, SDIO_FUNC_ENABLE_1 as u8).await; self.read8(FUNC_BUS, SDIOD_CCCR_IOEN).await as u32 == SDIO_FUNC_ENABLE_1 }, Duration::from_millis(500), ) .await { debug!("timeout while setting up the backplane"); return; } debug!("backplane is up"); // Read the bus width and set to 4 bits (1-bit bus is not currently supported) let reg = self.read8(FUNC_BUS, SDIOD_CCCR_BICTRL).await as u32; self.write8( FUNC_BUS, SDIOD_CCCR_BICTRL, ((reg & !BUS_SD_DATA_WIDTH_MASK) | BUS_SD_DATA_WIDTH_4BIT) as u8, ) .await; // Set the block size if !try_until( async || { self.write8(FUNC_BUS, SDIOD_CCCR_BLKSIZE_0, SDIO_64B_BLOCK as u8).await; self.read8(FUNC_BUS, SDIOD_CCCR_BLKSIZE_0).await as u32 == SDIO_64B_BLOCK }, Duration::from_millis(500), ) .await { debug!("timeout while setting block size"); return; } self.write8(FUNC_BUS, SDIOD_CCCR_BLKSIZE_0, SDIO_64B_BLOCK as u8).await; self.write8(FUNC_BUS, SDIOD_CCCR_F1BLKSIZE_0, SDIO_64B_BLOCK as u8) .await; self.write8(FUNC_BUS, SDIOD_CCCR_F2BLKSIZE_0, SDIO_64B_BLOCK as u8) .await; self.write8(FUNC_BUS, SDIOD_CCCR_F2BLKSIZE_1, 0).await; // Enable/Disable Client interrupts self.write8( FUNC_BUS, SDIOD_CCCR_INTEN, (INTR_CTL_MASTER_EN | INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN) as u8, ) .await; self.sdio.set_bus_to_high_speed(25_000_000).unwrap(); // enable more than 25MHz bus let reg = self.read8(FUNC_BUS, SDIOD_CCCR_SPEED_CONTROL).await as u32; if reg & 1 != 0 { self.write8(FUNC_BUS, SDIOD_CCCR_SPEED_CONTROL, (reg | SDIO_SPEED_EHS) as u8) .await; self.sdio.set_bus_to_high_speed(50_000_000).unwrap(); } // Wait till the backplane is ready if !try_until( async || self.read8(FUNC_BUS, SDIOD_CCCR_IORDY).await as u32 & SDIO_FUNC_READY_1 != 0, Duration::from_millis(500), ) .await { debug!("timeout while waiting for backplane to be ready"); return; } } async fn wlan_read(&mut self, buf: &mut Aligned) { self.cmd53_read(FUNC_WLAN, 0, buf).await; } async fn wlan_write(&mut self, buf: &Aligned) { self.cmd53_write(FUNC_WLAN, 0, buf).await; } #[allow(unused)] async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { trace!("bp_read addr = {:08x}, len = {}", addr, data.len()); // 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); assert!(data.as_ptr() as u32 % 4 == 0); // align buffer len // note: safe because align is checked above let mut data = aligned_mut(slice32_mut(unsafe { core::mem::transmute(data) })); 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(BLOCK_BUFFER_SIZE).min(window_remaining); let buf = &mut data[..len]; self.backplane_set_window(addr).await; self.cmd53_read(FUNC_BACKPLANE, addr & BACKPLANE_ADDRESS_MASK as u32, buf) .await; // Advance ptr. addr += len as u32; data = &mut data[len..]; } self.backplane_set_window(CHIPCOMMON_BASE_ADDRESS).await; } /// A.K.A. cyw43_download_resource async fn bp_write(&mut self, mut addr: u32, data: &[u8]) { trace!("bp_write addr = {:08x}, len = {}", addr, data.len()); // 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); assert!(data.as_ptr() as u32 % 4 == 0); // align buffer len // note: safe because align is checked above let mut data = aligned_ref(slice32_ref(unsafe { core::mem::transmute(data) })); 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(BLOCK_BUFFER_SIZE).min(window_remaining); let buf = &data[..len]; self.backplane_set_window(addr).await; self.cmd53_write(FUNC_BACKPLANE, addr & BACKPLANE_ADDRESS_MASK as u32, buf) .await; // Advance ptr. addr += len as u32; data = &data[len..]; } self.backplane_set_window(CHIPCOMMON_BASE_ADDRESS).await; // TODO: implement verify download } async fn bp_read8(&mut self, addr: u32) -> u8 { self.backplane_readn(addr, Word::U8).await as u8 } async fn bp_write8(&mut self, addr: u32, val: u8) { self.backplane_writen(addr, val as u32, Word::U8).await } async fn bp_read16(&mut self, addr: u32) -> u16 { self.backplane_readn(addr, Word::U16).await as u16 } #[allow(unused)] async fn bp_write16(&mut self, addr: u32, val: u16) { self.backplane_writen(addr, val as u32, Word::U16).await } #[allow(unused)] async fn bp_read32(&mut self, addr: u32) -> u32 { self.backplane_readn(addr, Word::U32).await } async fn bp_write32(&mut self, addr: u32, val: u32) { self.backplane_writen(addr, val, Word::U32).await } async fn read8(&mut self, func: u32, addr: u32) -> u8 { self.cmd52(false, func, addr, 0).await.into() } async fn write8(&mut self, func: u32, addr: u32, val: u8) { self.cmd52(true, func, addr, val).await; } async fn read16(&mut self, func: u32, addr: u32) -> u16 { let mut val = [0u32]; self.cmd53_read(func, addr, &mut aligned_mut(&mut val)[..2]).await; u16::from_be_bytes(val[0].to_be_bytes()[..2].try_into().unwrap()) } #[allow(unused)] async fn write16(&mut self, func: u32, addr: u32, val: u16) { self.cmd53_write(func, addr, &aligned_ref(&[val as u32])[..2]).await; } async fn read32(&mut self, func: u32, addr: u32) -> u32 { let mut val = [0u32]; self.cmd53_read(func, addr, &mut aligned_mut(&mut val)).await; val[0] } #[allow(unused)] async fn write32(&mut self, func: u32, addr: u32, val: u32) { self.cmd53_write(func, addr, &aligned_ref(&[val])).await; } async fn wait_for_event(&mut self) { Timer::after(Duration::from_millis(10)).await; // self.sdio.wait_for_event().await; } } ================================================ FILE: cyw43/src/spi.rs ================================================ use aligned::{A4, Aligned}; use embassy_futures::yield_now; use embassy_time::Timer; use embedded_hal_1::digital::OutputPin; use futures::FutureExt; use crate::consts::*; use crate::runner::{BusType, SealedBus}; use crate::util::{slice8_mut, slice32_mut, slice32_ref}; /// 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; } } /// Doc pub struct SpiBus { backplane_window: u32, pwr: PWR, spi: SPI, status: u32, } impl SpiBus where PWR: OutputPin, SPI: SpiBusCyw43, { pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { Self { backplane_window: 0xAAAA_AAAA, pwr, spi, status: 0, } } 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; } 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; } } impl SealedBus for SpiBus where PWR: OutputPin, SPI: SpiBusCyw43, { const TYPE: BusType = BusType::Spi; 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; } async fn wlan_read(&mut self, buf: &mut Aligned) { let len_in_u8 = buf.len() as u32; let buf = slice32_mut(buf); 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; } async fn wlan_write(&mut self, buf: &Aligned) { let buf = slice32_ref(buf); 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)] 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..]; } } 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..]; } } async fn bp_read8(&mut self, addr: u32) -> u8 { self.backplane_readn(addr, 1).await as u8 } async fn bp_write8(&mut self, addr: u32, val: u8) { self.backplane_writen(addr, val as u32, 1).await } async fn bp_read16(&mut self, addr: u32) -> u16 { self.backplane_readn(addr, 2).await as u16 } #[allow(unused)] async fn bp_write16(&mut self, addr: u32, val: u16) { self.backplane_writen(addr, val as u32, 2).await } #[allow(unused)] async fn bp_read32(&mut self, addr: u32) -> u32 { self.backplane_readn(addr, 4).await } async fn bp_write32(&mut self, addr: u32, val: u32) { self.backplane_writen(addr, val, 4).await } async fn read8(&mut self, func: u32, addr: u32) -> u8 { self.readn(func, addr, 1).await as u8 } async fn write8(&mut self, func: u32, addr: u32, val: u8) { self.writen(func, addr, val as u32, 1).await } async fn read16(&mut self, func: u32, addr: u32) -> u16 { self.readn(func, addr, 2).await as u16 } #[allow(unused)] async fn write16(&mut self, func: u32, addr: u32, val: u16) { self.writen(func, addr, val as u32, 2).await } async fn read32(&mut self, func: u32, addr: u32) -> u32 { if func == FUNC_BUS && addr == SPI_STATUS_REGISTER && self.status != 0 { let status = self.status; self.status = 0; status } else { self.readn(func, addr, 4).await } } #[allow(unused)] async fn write32(&mut self, func: u32, addr: u32, val: u32) { self.writen(func, addr, val, 4).await } async fn wait_for_event(&mut self) { self.spi.wait_for_event().await; } } 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) } ================================================ FILE: cyw43/src/structs.rs ================================================ 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(), )) } } ================================================ FILE: cyw43/src/util.rs ================================================ #![allow(unused)] use core::slice; use aligned::{A4, Aligned}; use embassy_time::{Duration, Ticker}; /// Defines a `repr(u8)` enum and implements a `from()` associated function to instantiate it from /// a `u8`, defaulting to the variant decorated with `#[default]`. macro_rules! enum_from_u8 { ( $( #[$enum_attr:meta] )* enum $enum:ident { // NOTE: The default variant must be the first variant. // Additionally, the `#[default]` attribute must be placed before any other attributes // on the variant, to avoid a parsing ambiguity. #[default] $( #[$default_variant_attr:meta] )* $default_variant:ident = $default_value:literal, $( $( #[$variant_attr:meta] )* $variant:ident = $value:literal ),+ $(,)? } ) => { $( #[$enum_attr] )* #[repr(u8)] pub enum $enum { $( #[$default_variant_attr] )* $default_variant = $default_value, $( $( #[$variant_attr] )* $variant = $value ),+ } impl $enum { pub fn from(value: u8) -> Self { match value { $default_value => Self::$default_variant, $( $value => Self::$variant ),+, _ => Self::$default_variant, } } } }; } pub(crate) use enum_from_u8; pub(crate) const fn slice8_mut(x: &mut [u32]) -> &mut [u8] { let len = size_of_val(x); unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } } pub(crate) const fn slice16_mut(x: &mut [u32]) -> &mut [u16] { let len = size_of_val(x) / 2; unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } } pub(crate) const fn aligned_mut(x: &mut [u32]) -> &mut Aligned { let len = size_of_val(x); unsafe { core::mem::transmute(slice::from_raw_parts_mut(x.as_mut_ptr() as *mut u8, len)) } } pub(crate) const fn aligned_ref(x: &[u32]) -> &Aligned { let len = size_of_val(x); unsafe { core::mem::transmute(slice::from_raw_parts(x.as_ptr() as *const u8, len)) } } pub(crate) const fn slice32_mut(x: &mut Aligned) -> &mut [u32] { let len = (size_of_val(x) + 3) / 4; unsafe { slice::from_raw_parts_mut(x as *mut Aligned as *mut u32, len) } } pub(crate) const fn slice32_ref(x: &Aligned) -> &[u32] { let len = (size_of_val(x) + 3) / 4; unsafe { slice::from_raw_parts(x as *const Aligned as *const u32, 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 } pub(crate) async fn try_until(mut func: impl AsyncFnMut() -> bool, duration: Duration) -> bool { let tick = Duration::from_millis(1); let mut ticker = Ticker::every(tick); let ticks = duration.as_ticks() / tick.as_ticks(); for _ in 0..ticks { if func().await { return true; } ticker.next().await; } false } ================================================ FILE: cyw43-firmware/.gitignore ================================================ *.exe *.pdb ================================================ FILE: cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt ================================================ 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. ================================================ FILE: cyw43-firmware/README.md ================================================ # 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). ================================================ FILE: cyw43-firmware/write_nvrams.rs ================================================ use std::fs::File; use std::io::{self, BufWriter, Write}; fn main() -> io::Result<()> { let nvrams = [ ( "rp2040", &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\x00"[..], ), ( "sterling_lwb+", &b"NVRAMRev=$Rev$\x00\ manfid=0x2d0\x00\ prodid=0x0727\x00\ vendid=0x14e4\x00\ devid=0x43e2\x00\ boardtype=0x0887\x00\ boardrev=0x1102\x00\ boardnum=22\x00\ macaddr=00:A0:50:b5:59:5e\x00\ sromrev=11\x00\ boardflags=0x00404001\x00\ boardflags3=0x08000000\x00\ xtalfreq=26000\x00\ nocrc=1\x00\ ag0=255\x00\ aa2g=1\x00\ rssicorrnorm=0\x00\ pa0itssit=0x20\x00\ extpagain2g=0\x00\ pa2ga0=-155,6912,-779\x00\ AvVmid_c0=0x0,0xc8\x00\ cckpwroffset0=5\x00\ maxp2ga0=78\x00\ txpwrbckof=6\x00\ cckbw202gpo=0\x00\ legofdmbw202gpo=0x40000000\x00\ mcsbw202gpo=0x60000000\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\ il0macaddr=00:90:4c:c5:12:38\x00\ wl0id=0x431b\x00\ deadman_to=0xffffffff\x00\ muxenab=0x1\x00\ spurconfig=0x3\x00\ glitch_based_crsmin=1\x00\ btc_mode=0\x00\ bt_default_ant=0\x00\ edonthd20l=-72\x00\ edoffthd20ul=-78\x00\ \x00\x00"[..], ), ]; for (name, bytes) in nvrams { let file = File::create(format!("nvram_{}.bin", name))?; let mut writer = BufWriter::new(file); writer.write_all(bytes)?; writer.flush()?; println!("Wrote {}", name); } Ok(()) } ================================================ FILE: cyw43-pio/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.10.0 - 2026-03-10 - Updated to use new DMA `Channel` driver struct from embassy-rp - Update cyw43 0.7.0 - Update embassy-rp 0.10.0 ## 0.9.0 - 2025-11-27 - Select pio program based on core clock speed #4792 ## 0.8.0 - 2025-08-28 - Bump cyw43 version ## 0.7.0 - 2025-08-26 ## 0.6.0 - 2025-08-04 ## 0.5.1 - 2025-07-16 ## 0.5.0 - 2025-07-15 - Update embassy-rp to 0.5.0 ## 0.3.0 - 2025-01-05 - Update embassy-time to 0.4.0 - Update cyw43 to 0.3.0 - Update embassy-rp to 0.3.0 ## 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 ================================================ FILE: cyw43-pio/Cargo.toml ================================================ [package] name = "cyw43-pio" version = "0.10.0" edition = "2024" 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" [dependencies] cyw43 = { version = "0.7.0", path = "../cyw43" } embassy-rp = { version = "0.10.0", path = "../embassy-rp" } fixed = "1.23.1" defmt = { version = "1.0.1", optional = true } [features] defmt = ["dep:defmt"] [package.metadata.embassy] build = [ {target = "thumbv6m-none-eabi", features = ["embassy-rp/rp2040"]}, ] [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"] ================================================ FILE: cyw43-pio/README.md ================================================ # 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. ================================================ FILE: cyw43-pio/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::Peri; use embassy_rp::clocks::clk_sys_freq; use embassy_rp::dma::Channel; use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; use fixed::FixedU32; use fixed::types::extra::U8; /// SPI comms driven by PIO. pub struct PioSpi<'d, PIO: Instance, const SM: usize> { cs: Output<'d>, sm: StateMachine<'d, PIO, SM>, irq: Irq<'d, PIO, 0>, dma: Channel<'d>, wrap_target: u8, } /// Clock divider used for most applications /// With default core clock configuration: /// RP2350: 150Mhz / 2 = 75Mhz pio clock -> 37.5Mhz GSPI clock /// RP2040: 133Mhz / 2 = 66.5Mhz pio clock -> 33.25Mhz GSPI clock pub const DEFAULT_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0200); /// Clock divider used to overclock the cyw43 /// With default core clock configuration: /// RP2350: 150Mhz / 1 = 150Mhz pio clock -> 75Mhz GSPI clock (50% greater that manufacturer /// recommended 50Mhz) /// RP2040: 133Mhz / 1 = 133Mhz pio clock -> 66.5Mhz GSPI clock (33% greater that manufacturer /// recommended 50Mhz) pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0100); /// Clock divider used with the RM2 /// With default core clock configuration: /// RP2350: 150Mhz / 3 = 50Mhz pio clock -> 25Mhz GSPI clock /// RP2040: 133Mhz / 3 = 44.33Mhz pio clock -> 22.16Mhz GSPI clock pub const RM2_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0300); impl<'d, PIO, const SM: usize> PioSpi<'d, PIO, SM> where PIO: Instance, { /// Create a new instance of PioSpi. pub fn new( common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, clock_divider: FixedU32, irq: Irq<'d, PIO, 0>, cs: Output<'d>, dio: Peri<'d, impl PioPin>, clk: Peri<'d, impl PioPin>, dma: Channel<'d>, ) -> Self { let effective_pio_frequency = (clk_sys_freq() as f32 / clock_divider.to_num::()) as u32; #[cfg(feature = "defmt")] defmt::trace!("Effective pio frequency: {}Hz", effective_pio_frequency); // Non-integer pio clock dividers are achieved by introducing clock jitter resulting in a // combination of long and short cycles. The long and short cycles average to achieve the // requested clock speed. // This can be a problem for peripherals that expect a consistent clock / have a clock // speed upper bound that is violated by the short cycles. The cyw43 seems to handle the // jitter well, but we emit a warning to recommend an integer divider anyway. if clock_divider.frac() != FixedU32::::ZERO { #[cfg(feature = "defmt")] defmt::trace!( "Configured clock divider is not a whole number. Some clock cycles may violate the maximum recommended GSPI speed. Use at your own risk." ); } // Different pio programs must be used for different pio clock speeds. // The programs used below are based on the pico SDK: https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio // The clock speed cutoff for each program has been determined experimentally: // > 100Mhz -> Overclock program // [75Mhz, 100Mhz] -> High speed program // [0, 75Mhz) -> Low speed program let loaded_program = if effective_pio_frequency > 100_000_000 { // Any frequency > 100Mhz is overclocking the chip (manufacturer recommends max 50Mhz GSPI // clock) // Example: // * RP2040 @ 133Mhz (stock) with OVERCLOCK_CLOCK_DIVIDER (133MHz) #[cfg(feature = "defmt")] defmt::trace!( "Configured clock divider results in a GSPI frequency greater than the manufacturer recommendation (50Mhz). Use at your own risk." ); let overclock_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" "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" ); common.load_program(&overclock_program.program) } else if effective_pio_frequency >= 75_000_000 { // Experimentally determined cutoff. // Notably includes the stock RP2350 configured with clk_div of 2 (150Mhz base clock / 2 = 75Mhz) // but does not include stock RP2040 configured with clk_div of 2 (133Mhz base clock / 2 = 66.5Mhz) // Example: // * RP2350 @ 150Mhz (stock) with DEFAULT_CLOCK_DIVIDER (75Mhz) // * RP2XXX @ 200Mhz with DEFAULT_CLOCK_DIVIDER (100Mhz) #[cfg(feature = "defmt")] defmt::trace!("Using high speed pio program."); let high_speed_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" // read in y-1 bits "lp2:" "in pins, 1 side 0" "jmp y-- lp2 side 1" // wait for event and irq host "wait 1 pin 0 side 0" "irq 0 side 0" ".wrap" ); common.load_program(&high_speed_program.program) } else { // Low speed // Examples: // * RP2040 @ 133Mhz (stock) with DEFAULT_CLOCK_DIVIDER (66.5Mhz) // * RP2040 @ 133Mhz (stock) with RM2_CLOCK_DIVIDER (44.3Mhz) // * RP2350 @ 150Mhz (stock) with RM2_CLOCK_DIVIDER (50Mhz) #[cfg(feature = "defmt")] defmt::trace!("Using low speed pio program."); let low_speed_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" ); common.load_program(&low_speed_program.program) }; 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(); 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.clock_divider = clock_divider; 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, 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 { self.sm.set_x(write_bits as u32); self.sm.set_y(read_bits as u32); self.sm.set_pindir(0b1); self.sm.exec_jmp(self.wrap_target); } self.sm.set_enable(true); self.sm.tx().dma_push(&mut self.dma, write, false).await; let mut status = 0; self.sm .rx() .dma_pull(&mut self.dma, slice::from_mut(&mut status), false) .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 { self.sm.set_y(read_bits as u32); self.sm.set_x(write_bits as u32); self.sm.set_pindir(0b1); self.sm.exec_jmp(self.wrap_target); } // self.cs.set_low(); self.sm.set_enable(true); self.sm.tx().dma_push(&mut self.dma, slice::from_ref(&cmd), false).await; self.sm.rx().dma_pull(&mut self.dma, read, false).await; let mut status = 0; self.sm .rx() .dma_pull(&mut self.dma, slice::from_mut(&mut status), false) .await; #[cfg(feature = "defmt")] defmt::trace!("cmd_read cmd = {:02x} len = {} read = {:08x}", cmd, read.len(), read); status } } impl<'d, PIO, const SM: usize> SpiBusCyw43 for PioSpi<'d, PIO, SM> where PIO: Instance, { 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; } } ================================================ FILE: docs/Makefile ================================================ all: asciidoctor -d book -D book/ index.adoc cp -r images book clean: rm -rf book .PHONY: all clean ================================================ FILE: docs/README.md ================================================ # 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 ================================================ FILE: docs/examples/basic/.cargo/config.toml ================================================ [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" ================================================ FILE: docs/examples/basic/Cargo.toml ================================================ [package] authors = ["Dario Nieuwenhuis "] edition = "2024" name = "embassy-basic-example" version = "0.1.0" license = "MIT OR Apache-2.0" publish = false [dependencies] embassy-executor = { version = "0.10.0", path = "../../../embassy-executor", features = ["defmt", "platform-cortex-m", "executor-thread"] } embassy-time = { version = "0.5.1", path = "../../../embassy-time", features = ["defmt"] } embassy-nrf = { version = "0.10.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } defmt = "1.0.1" defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } [package.metadata.embassy] build = [ { target = "thumbv7em-none-eabi" } ] ================================================ FILE: docs/examples/basic/build.rs ================================================ //! 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"); } ================================================ FILE: docs/examples/basic/memory.x ================================================ 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 } ================================================ FILE: docs/examples/basic/src/main.rs ================================================ #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; use embassy_nrf::Peri; use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pull}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; // Declare async tasks #[embassy_executor::task] async fn blink(pin: Peri<'static, 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) { // Initialize the embassy-nrf HAL. let p = embassy_nrf::init(Default::default()); // Spawned tasks run in the background, concurrently. spawner.spawn(blink(p.P0_13.into()).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!"); } } ================================================ FILE: docs/examples/layer-by-layer/.cargo/config.toml ================================================ [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" rustflags = [ "-C", "link-arg=--nmagic", "-C", "link-arg=-Tlink.x", "-C", "link-arg=-Tdefmt.x", ] [build] target = "thumbv7em-none-eabihf" [env] DEFMT_LOG = "trace" ================================================ FILE: docs/examples/layer-by-layer/Cargo.toml ================================================ [workspace] resolver = "2" members = [ "blinky-pac", "blinky-hal", "blinky-async", ] [profile.release] codegen-units = 1 debug = 2 debug-assertions = false incremental = false lto = "fat" opt-level = 's' overflow-checks = false ================================================ FILE: docs/examples/layer-by-layer/blinky-async/Cargo.toml ================================================ [package] name = "blinky-async" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" publish = false [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embassy-stm32 = { version = "0.6.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x", "exti"] } embassy-executor = { version = "0.10.0", path = "../../../../embassy-executor", features = ["platform-cortex-m", "executor-thread"] } defmt = "1.0.1" defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } [package.metadata.embassy] build = [ { target = "thumbv7em-none-eabi" } ] ================================================ FILE: docs/examples/layer-by-layer/blinky-async/src/main.rs ================================================ #![no_std] #![no_main] use embassy_executor::Spawner; use embassy_stm32::exti::{self, ExtiInput}; use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_stm32::{bind_interrupts, interrupt}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!( pub struct Irqs{ EXTI15_10 => exti::InterruptHandler; }); #[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, Irqs); loop { button.wait_for_any_edge().await; if button.is_low() { led.set_high(); } else { led.set_low(); } } } ================================================ FILE: docs/examples/layer-by-layer/blinky-hal/Cargo.toml ================================================ [package] name = "blinky-hal" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" publish = false [dependencies] cortex-m-rt = "0.7" cortex-m = { version = "0.7", features = ["critical-section-single-core"] } embassy-stm32 = { version = "0.6.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x"] } defmt = "1.0.1" defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } [package.metadata.embassy] build = [ { target = "thumbv7em-none-eabi" } ] ================================================ FILE: docs/examples/layer-by-layer/blinky-hal/src/main.rs ================================================ #![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(); } } } ================================================ FILE: docs/examples/layer-by-layer/blinky-irq/Cargo.toml ================================================ [workspace] [package] name = "blinky-irq" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" publish = false [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = { version = "0.7" } embassy-stm32 = { version = "0.6.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x", "unstable-pac"] } defmt = "1.0.1" defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } [package.metadata.embassy] build = [ { target = "thumbv7em-none-eabi" } ] ================================================ FILE: docs/examples/layer-by-layer/blinky-irq/src/main.rs ================================================ #![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)); }); } ================================================ FILE: docs/examples/layer-by-layer/blinky-pac/Cargo.toml ================================================ [package] name = "blinky-pac" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" publish = false [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" stm32-metapac = { version = "21", features = ["stm32l475vg"] } defmt = "1.0.1" defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } [package.metadata.embassy] build = [ { target = "thumbv7em-none-eabi" } ] ================================================ FILE: docs/examples/layer-by-layer/blinky-pac/src/main.rs ================================================ #![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; 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; gpioc.pupdr().modify(|w| w.set_pupdr(BUTTON_PIN, vals::Pupdr::PULL_UP)); gpioc.otyper().modify(|w| w.set_ot(BUTTON_PIN, vals::Ot::PUSH_PULL)); gpioc.moder().modify(|w| w.set_moder(BUTTON_PIN, vals::Moder::INPUT)); // Setup LED let gpiob = pac::GPIOB; const LED_PIN: usize = 14; gpiob.pupdr().modify(|w| w.set_pupdr(LED_PIN, vals::Pupdr::FLOATING)); gpiob.otyper().modify(|w| w.set_ot(LED_PIN, vals::Ot::PUSH_PULL)); gpiob.moder().modify(|w| w.set_moder(LED_PIN, vals::Moder::OUTPUT)); // Main loop loop { 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)); } } } ================================================ FILE: docs/examples/layer-by-layer/memory.x ================================================ MEMORY { FLASH : ORIGIN = 0x08000000, LENGTH = 2048K /* BANK_1 */ RAM : ORIGIN = 0x20000000, LENGTH = 640K /* SRAM */ } ================================================ FILE: docs/images/embassy_executor.drawio ================================================ 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= ================================================ FILE: docs/images/embassy_irq.drawio ================================================ 5VlNc9owEP01HJOxLdsxx0Bokpk0zZRM0p4ywl6wBmMRWWDIr6+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== ================================================ FILE: docs/index.adoc ================================================ :description: Embassy Book :sectanchors: :doctype: book :toc: :toc-placement: left :toclevels: 2 :imagesdir: images :source-highlighter: rouge # Embassy Book Welcome to the Embassy Book. The Embassy Book is for everyone who wants to use Embassy and understand how Embassy works. include::pages/overview.adoc[leveloffset = 1] include::pages/beginners.adoc[leveloffset = 1] include::pages/developer.adoc[leveloffset = 1] include::pages/system.adoc[leveloffset = 1] include::pages/faq.adoc[leveloffset = 1] ================================================ FILE: docs/pages/basic_application.adoc ================================================ = A basic Embassy application So you've got one of the examples running, but what now? Let's go through a simple Embassy application for the nRF52 DK to understand it better. == Main The full example can be found link:https://github.com/embassy-rs/embassy/tree/main/docs/examples/basic[here]. NOTE: If you’re using VS Code and rust-analyzer to view and edit the examples, you may need to make some changes to `.vscode/settings.json` to tell it which project we’re working on. Follow the instructions commented in that file to get rust-analyzer working correctly. === Bare metal The first thing you’ll notice are two attributes at the top of the file. These tell the compiler that the program has no access to std, and that there is no main function (because it is not run by an OS). [source,rust] ---- include::../examples/basic/src/main.rs[lines="1..2"] ---- === Dealing with errors Then, what follows are some declarations on how to deal with panics and faults. During development, a good practice is to rely on `defmt-rtt` and `panic-probe` to print diagnostics to the terminal: [source,rust] ---- include::../examples/basic/src/main.rs[lines="9"] ---- === Task declaration After a bit of import declaration, the tasks run by the application should be declared: [source,rust] ---- include::../examples/basic/src/main.rs[lines="12..23"] ---- An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking. NOTE: Notice that there is no busy waiting going on in this task. It is using the Embassy timer to yield execution, allowing the microcontroller to sleep in between the blinking. === Main The main entry point of an Embassy application is defined using the `#[embassy_executor::main]` macro. The entry point is passed a `Spawner`, which it can use to spawn other tasks. We then initialize the HAL with a default config, which gives us a `Peripherals` struct we can use to access the MCU’s various peripherals. In this case, we want to configure one of the pins as a GPIO output driving the LED: [source,rust] ---- include::../examples/basic/src/main.rs[lines="26..-1"] ---- What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy_executor::main]` macro. The macro does the following: . Creates an Embassy Executor . Defines a main task for the entry point . Runs the executor spawning the main task There is also a way to run the executor without using the macro, in which case you have to create the `Executor` instance yourself. == The Cargo.toml The project definition needs to contain the embassy dependencies: [source,toml] ---- include::../examples/basic/Cargo.toml[lines="10..12"] ---- Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well). In this particular case, the nrf52840 chip is selected, and the RTC1 peripheral is used as the time driver. ================================================ FILE: docs/pages/beginners.adoc ================================================ = For beginners The articles in this section are primarily aimed at users new to Embassy, showing how to get started, how to structure your project and other best practices. include::getting_started.adoc[leveloffset = 2] include::basic_application.adoc[leveloffset = 2] include::project_structure.adoc[leveloffset = 2] include::new_project.adoc[leveloffset = 2] include::best_practices.adoc[leveloffset = 2] include::layer_by_layer.adoc[leveloffset = 2] == What's next? Now that you're familiar with the basics, the xref:_system_description[System description] section covers the executor, HALs, bootloader, and other Embassy components in more detail. ================================================ FILE: docs/pages/best_practices.adoc ================================================ = Best Practices Over time, a couple of best practices have emerged. The following list should serve as a guideline for developers writing embedded software in _Rust_, especially in the context of the _Embassy_ framework. == Passing Buffers by Reference It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't want to spend resources on an allocator and end up placing buffers on the stack. This, however, can easily blow up your stack if you are not careful. Consider the following example: [,rust] ---- fn process_buffer(mut buf: [u8; 1024]) -> [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 safety 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 () } ---- ================================================ FILE: docs/pages/bootloader.adoc ================================================ = Bootloader `embassy-boot` is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. The update method used is referred to as an A/B partition update scheme. With a general-purpose OS, A/B partition update is accomplished by directly booting either the A or B partition depending on the update state. To accomplish the same goal in a way that is portable across all microcontrollers, `embassy-boot` swaps data page by page (in both directions) between the DFU and the Active partition when a firmware update is triggered. + Because the original Active application is moved into the DFU partition during this update, the operation can be reversed if the update is interrupted or the new firmware does not flag that it booted successfully. + See the design section for more details on how this is implemented. 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 is given by: + Partition Size~state~ = (2 × Write Size~state~) + (4 × Write Size~state~ × 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 two words, plus four words per page in the ACTIVE partition. 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. ================================================ FILE: docs/pages/developer.adoc ================================================ = For developers This section covers the internals of Embassy, aimed at contributors and developers who want to understand how Embassy is built or add support for new hardware. include::developer_stm32.adoc[leveloffset = 2] ================================================ FILE: docs/pages/developer_stm32.adoc ================================================ = 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 a 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 achieve 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 are 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. ================================================ FILE: docs/pages/embassy_in_the_wild.adoc ================================================ = 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]! _newer entries at the top_ * link:https://github.com/tendan/remote_rc_bt_esp32[ESPress WheeLE: Remote-Controlled Rover via Bluetooth LE] ** Robust, energy-efficient, and safety-focused remote control solution for small-scale robotic platforms using instructions received via Bluetooth LE. * link:https://github.com/thataquarel/protovolt[ProtoV MINI: A USB-C mini lab power supply] ** A dual-channel USB PD powered breadboard power supply based on the RP2040, running embedded graphics. Open-source schematics and firmware. * link:https://github.com/Dawson-HEP/opentrig/[Opentrig: A particle physics trigger and data acquisition system] ** Digital event trigger with threshold, data acquisition system designed to interface with AIDA-2020 TLU systems, tested at the DESY II Test Beam Facility. Based on the RP2040, and Embassy's async event handling. * link:https://github.com/1-rafael-1/air-quality-monitor[Air Quality Monitor] ** Air Quality Monitor based on rp2350 board, ens160 and aht21 sensors and ssd1306 display. Code and 3D printable enclosure included. * link:https://github.com/CarlKCarlK/clock[Embassy Clock: Layered, modular bare-metal clock with emulation] ** A `no_std` Raspberry Pi Pico clock demonstrating layered Embassy tasks (Display->Blinker->Clock) for clean separation of multiplexing, blinking, and UI logic. Features single-button HH:MM/MM:SS time-set UI, heapless data structures, and a Renode emulator for hardware-free testing. See link:https://medium.com/@carlmkadie/how-rust-embassy-shine-on-embedded-devices-part-2-aad1adfccf72[this article] for details. * 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]] ================================================ FILE: docs/pages/examples.adoc ================================================ = 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[] ---- ================================================ FILE: docs/pages/faq.adoc ================================================ = 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 or RP235x without a debugging probe. Install link:https://github.com/raspberrypi/pico-sdk-tools/releases[Picotool] for uploading the binary. 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 = "picotool load --update --verify --execute -t elf" ---- Picotool will detect your device and upload the binary, skipping identical flash sectors (the `--update` command-line flag), verify that the binary was written correctly (`--verify`), and then run your new code (`--execute`). Run `picotool help load` for more information. == 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 link:https://crates.io/crates/esp-hal[esp-hal]. == 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_schedule_wake >>> referenced by driver.rs:144 (src/driver.rs:144) >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::schedule_wake::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 _; ---- Another common error you may experience is: [source,text] ---- = note: rust-lld: error: undefined symbol: __pender >>> referenced by mod.rs:373 (src/raw/mod.rs:373) >>> embassy_executor-e78174e249bca7f4.embassy_executor.1e9d60fc90940543-cgu.0.rcgu.o:(embassy_executor::raw::Pender::pend::h0f19b6e01762e4cd) in archive [...]libembassy_executor-e78174e249bca7f4.rlib ---- There are two possible causes to this error: * You are using `embassy-executor` without enabling one of the architecture-specific features, but you are using a HAL that does not bring its own executors. For example, for Cortex-M (like the RP2040), you need to enable the `arch-cortex-m` feature of `embassy-executor`. * You are not using `embassy-executor`. In this case, you need to enable the one of the `generic-queue-X` features of `embassy-time`. == 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-utils = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" } embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" } # embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" } ---- 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"]` * 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. == 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. You may 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]. Or you may define a struct implementing the `embassy-[family]::interrupt::typelevel::Handler` trait with an on_interrupt() method, and bind it to the interrupt vector via the `bind_interrupts!` macro, which introduces only a single indirection. This allows the mixing of manual ISRs with Embassy driver-defined ISRs; handlers will be called directly in the order they appear in the macro. == 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` from `cargo-binutils` 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 link:https://github.com/embassy-rs/stm32-data/issues/301[this Github issue] 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 does 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 `#[unsafe(link_section = ".xxx")]` 5. Copy data to that region before using BDMA. See link:https://github.com/embassy-rs/embassy/blob/main/examples/stm32h7/src/bin/spi_bdma.rs[SMT32H7 SPI BDMA example] for more details. == 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. ================================================ FILE: docs/pages/getting_started.adoc ================================================ = 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. * link:https://github.com/knurling-rs/flip-link[flip-link] - to link certain example binaries with stack overflow protection. 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 the examples folder for your board 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-rs 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 are 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]. ================================================ FILE: docs/pages/hal.adoc ================================================ = 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 and RP235x 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. For the Microchip PolarFire SoC, there is link:https://github.com/AlexCharlton/mpfs-hal[mpfs-hal]. For the Puya Semiconductor PY32 series, there is link:https://github.com/py32-rs/py32-hal[py32-hal]. ================================================ FILE: docs/pages/imxrt.adoc ================================================ = Embassy iMXRT HAL The link:https://github.com/embassy-rs/embassy/tree/main/embassy-imxrt[Embassy iMXRT HAL] is based on the following PACs (Peripheral Access Crate): * link:https://github.com/OpenDevicePartnership/mimxrt685s-pac[mimxrt685s-pac] * link:https://github.com/OpenDevicePartnership/mimxrt633s-pac[mimxrt633s-pac] == Peripherals The following peripherals have a HAL implementation at present * CRC * DMA * GPIO * RNG * UART ================================================ FILE: docs/pages/layer_by_layer.adoc ================================================ = 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 little to prevent you from configuring or coordinating those registers incorrectly. 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.wait_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. On this chip, interrupt signals on EXTI lines 10-15 (including the button on EXTI line 13) raise the hardware interrupt EXTI15_10. This interrupt handler has been bound (using `bind_interrupts!`) to call the `InterruptHandler` provided by the exti module, so that whenever an interrupt is raised, the task awaiting the button via `wait_for_any_edge()` 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. ================================================ FILE: docs/pages/mcxa.adoc ================================================ = Embassy MCX-A HAL The link:https://github.com/embassy-rs/embassy/tree/main/embassy-mcxa[Embassy MCX-A HAL] is based on the following PAC (Peripheral Access Crate): * link:https://github.com/embassy-rs/nxp-pac[nxp-pac] == Peripherals The following peripherals have a HAL implementation at present * Clocks * GPIO * ADC * CLKOUT * I2C * I3C * LPUart * OSTimer * RTC * SPI controller * TRNG * WWDT * CDOG * CTIMER * CRC * DMA ================================================ FILE: docs/pages/microchip.adoc ================================================ = Embassy Microchip HAL The link:https://github.com/embassy-rs/embassy/tree/main/embassy-microchip[Embassy Microchip HAL] is based on the following PAC (Peripheral Access Crate): * link:https://github.com/OpenDevicePartnership/mec17xx-pac[mec17xx-pac] == Peripherals The following peripherals have a HAL implementation at present * GPIO * I2C * PWM * TACH * Time driver (using the CCT) ================================================ FILE: docs/pages/new_project.adoc ================================================ = 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) === esp-generate - link:https://github.com/esp-rs/esp-generate[esp-generate] (ESP32 using esp-hal) == 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 be added in 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", "platform-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } ---- Previously, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specific revision of an embassy crate which is not yet published. The recommended way of doing so is: * 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 follows. Note that the `version` values must match the versions defined in the embassy crates at the git revision you are checking out, which may differ from the latest published versions on crates.io: [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 = ["platform-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.3.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 `[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 which toolchain to use. 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 = "1.85" 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 ---- ================================================ FILE: docs/pages/nrf.adoc ================================================ = 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. ================================================ FILE: docs/pages/overview.adoc ================================================ = 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 if they do not transfer control back to the kernel within an allotted time. If tasks could instead be presumed to behave cooperatively, it would be possible to create tasks that are 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, nRF54, nRF91 series. * link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 as well as RP235x microcontroller. * link:https://docs.embassy.dev/embassy-mspm0/[embassy-mspm0], for the Texas Instruments MSPM0 microcontrollers. * link:https://github.com/esp-rs/esp-hal[esp-hal], 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. * link:https://github.com/AlexCharlton/mpfs-hal[mpfs-hal], for the Microchip PolarFire SoC. * link:https://github.com/py32-rs/py32-hal[py32-hal], for the Puya Semiconductor PY32 series of chips. NOTE: A common question is if one can use the Embassy HALs standalone. Yes, it is possible! There is 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/trouble[trouble] crate provides a Bluetooth Low Energy 4.x and 5.x Host that runs on any microcontroller implementing the link:https://github.com/embassy-rs/bt-hci[bt-hci] traits (currently `nRF52`, `nrf54`, `rp2040`, `rp23xx` and `esp32` and `serial` controllers are supported). * 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[Comparison 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=pDd5mXBF4tY[Intro to Embassy] * link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] ================================================ FILE: docs/pages/project_structure.adoc ================================================ = 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 = "1.85" # <- as of writing, this is the exact rust version embassy uses components = [ "rust-src", "rustfmt" ] # <- optionally add "llvm-tools-preview" for some extra features like "cargo size" targets = [ "thumbv6m-none-eabi" # <- change for your platform ] ---- ================================================ FILE: docs/pages/runtime.adoc ================================================ = 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. Tasks 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. * Multiple executor instances can be created, to run tasks at different priority levels. This allows higher-priority tasks to preempt lower-priority tasks. == Executor The executor works as follows: . When a task is created, it is polled. . The task attempts 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 returning `Poll::Pending`. . Once a task yields, the executor enqueues the task at the end of the run queue and proceeds to 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, this would prevent the executor regaining control and scheduling 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 updates 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: A special executor named `InterruptExecutor` 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 that a time `Driver` implementation exists for the platform. Embassy provides time drivers for the nRF, STM32, RPi Pico, WASM and Std platforms. NOTE: Some embedded platforms' timer drivers might only support a limited number of alarms. Make sure that the number of tasks using the timer simultaneously 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. ================================================ FILE: docs/pages/sharing_peripherals.adoc ================================================ = Sharing peripherals between tasks Often, more than one task needs to access 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 show different ways for two tasks to use the on-board LED on a Raspberry Pi Pico board 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]. ================================================ FILE: docs/pages/stm32.adoc ================================================ = 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. ================================================ FILE: docs/pages/system.adoc ================================================ = 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::imxrt.adoc[leveloffset = 2] include::mcxa.adoc[leveloffset = 2] include::nrf.adoc[leveloffset = 2] include::stm32.adoc[leveloffset = 2] include::sharing_peripherals.adoc[leveloffset = 2] ================================================ FILE: docs/pages/time_keeping.adoc ================================================ = 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; 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; use embassy_time::Delay; #[task] /// Task that ticks periodically async fn tick_periodic() -> ! { loop { rprintln!("tick!"); // async sleep primitive, suspends the task for 500ms. generic_delay(Delay).await } } async fn generic_delay(delay: D) { delay.delay_ms(500).await; } ---- ================================================ FILE: embassy-boot/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.7.0 - 2026-03-10 - Fixed documentation and assertion of STATE partition size requirements - Added documentation for package features - Made `read_state` on `BootLoader` public - Update embassy-embedded-hal to 0.6.0 - Update embassy-sync to 0.8.0 ## 0.6.1 - 2025-08-26 - First release with changelog. ================================================ FILE: embassy-boot/Cargo.toml ================================================ [package] edition = "2024" name = "embassy-boot" version = "0.7.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 = "1.0.1", optional = true } digest = "0.10" document-features = "0.2.7" log = { version = "0.4", optional = true } ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true } embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal" } embassy-sync = { version = "0.8.0", 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] ## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging defmt = ["dep:defmt"] ## Use log for logging log = ["dep:log"] ## Enable for devices that set erased flash bytes to `0x00` instead of the usual `0xFF` flash-erase-zero = [] #! ## Firmware Signing #! Enable one of these features to allow verification of DFU signatures with #! `FirmwareUpdater::verify_and_mark_updated`. ## Use the `ed25519-dalek` package to verify DFU signatures. ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ## Use the `salty` package to verify DFU signatures. ed25519-salty = ["dep:salty", "_verify"] #Internal features _verify = [] ================================================ FILE: embassy-boot/README.md ================================================ # 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. ================================================ FILE: embassy-boot/src/boot_loader.rs ================================================ use core::cell::RefCell; use embassy_embedded_hal::flash::partition::BlockingPartition; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; use crate::{DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC, State}; /// 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 { unsafe 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. /// N = Active partition size divided by WRITE_SIZE. /// | 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 + 2N) | Progress index used while swapping | /// | (2 + 2N)..(2 + 4N) | Progress index used while 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) } /// Read the magic state from flash pub 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 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 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 + 4 * (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); } } ================================================ FILE: embassy-boot/src/digest_adapters/ed25519_dalek.rs ================================================ 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 {} ================================================ FILE: embassy-boot/src/digest_adapters/mod.rs ================================================ #[cfg(feature = "ed25519-dalek")] pub(crate) mod ed25519_dalek; #[cfg(feature = "ed25519-salty")] pub(crate) mod salty; ================================================ FILE: embassy-boot/src/digest_adapters/salty.rs ================================================ 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 {} ================================================ FILE: embassy-boot/src/firmware_updater/asynch.rs ================================================ 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::{BOOT_MAGIC, DFU_DETACH_MAGIC, FirmwareUpdaterError, STATE_ERASE_VALUE, SWAP_MAGIC, State}; /// 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 { unsafe 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(()) } /// Read a slice of data from the DFU storage peripheral, starting the read /// operation at the given address offset, and reading `buf.len()` bytes. /// /// # Errors /// /// Returns an error if the arguments are not aligned or out of bounds. pub async fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> { self.dfu.read(offset, buf).await?; 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); } } ================================================ FILE: embassy-boot/src/firmware_updater/blocking.rs ================================================ 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::{BOOT_MAGIC, DFU_DETACH_MAGIC, FirmwareUpdaterError, STATE_ERASE_VALUE, SWAP_MAGIC, State}; /// 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 { unsafe 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(()) } /// Read a slice of data from the DFU storage peripheral, starting the read /// operation at the given address offset, and reading `buf.len()` bytes. /// /// # Errors /// /// Returns an error if the arguments are not aligned or out of bounds. pub fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> { self.dfu.read(offset, buf)?; 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::Mutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex; 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); } } ================================================ FILE: embassy-boot/src/firmware_updater/mod.rs ================================================ 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()) } } ================================================ FILE: embassy-boot/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-boot/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![allow(unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] #![doc = include_str!("../README.md")] //! ## Feature flags #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] 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() ); } } ================================================ FILE: embassy-boot/src/mem_flash.rs ================================================ #![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) } } ================================================ FILE: embassy-boot/src/test_flash/asynch.rs ================================================ 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) } } ================================================ FILE: embassy-boot/src/test_flash/blocking.rs ================================================ use core::cell::RefCell; use embassy_embedded_hal::flash::partition::BlockingPartition; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex; 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) } } ================================================ FILE: embassy-boot/src/test_flash/mod.rs ================================================ mod asynch; mod blocking; pub(crate) use asynch::AsyncTestFlash; pub(crate) use blocking::BlockingTestFlash; ================================================ FILE: embassy-boot-nrf/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.11.0 - 2026-03-10 - Update embassy-sync to 0.8.0 - Update embassy-nrf to 0.10.0 - Update embassy-boot to 0.7.0 ## 0.10.0 - 2025-12-15 - Bumped embassy-nrf to 0.9.0 ## 0.9.0 - 2025-09-30 - Bumped embassy-nrf to 0.8.0 ## 0.8.0 - 2025-08-26 ## 0.1.1 - 2025-08-15 - First release with changelog. ================================================ FILE: embassy-boot-nrf/Cargo.toml ================================================ [package] edition = "2024" name = "embassy-boot-nrf" version = "0.11.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] build = [ {target = "thumbv7em-none-eabi", features = ["embassy-nrf/nrf52840"]}, {target = "thumbv8m.main-none-eabihf", features = ["embassy-nrf/nrf5340-app-s"]}, {target = "thumbv8m.main-none-eabihf", features = ["embassy-nrf/nrf9160-ns"]}, {target = "thumbv8m.main-none-eabihf", features = ["embassy-nrf/nrf9120-ns"]}, {target = "thumbv8m.main-none-eabihf", features = ["embassy-nrf/nrf9151-ns"]}, {target = "thumbv8m.main-none-eabihf", features = ["embassy-nrf/nrf9161-ns"]}, ] [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 = "1.0.1", optional = true } log = { version = "0.4.17", optional = true } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-nrf = { version = "0.10.0", path = "../embassy-nrf", default-features = false } embassy-boot = { version = "0.7.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", ] log = ["dep:log"] softdevice = [ "dep:nrf-softdevice-mbr", ] ================================================ FILE: embassy-boot-nrf/README.md ================================================ # 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. ================================================ FILE: embassy-boot-nrf/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-boot-nrf/src/lib.rs ================================================ #![no_std] #![allow(unsafe_op_in_unsafe_fn)] #![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::{Peri, 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: Peri<'static, impl wdt::Instance>, 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() } } ================================================ FILE: embassy-boot-rp/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.10.0 - 2026-03-10 - Update embassy-sync 0.8.0 - Update embassy-rp 0.10.0 - Update embassy-boot 0.7.0 ## 0.9.0 - 2025-11-27 ## 0.8.0 - 2025-08-26 ## 0.1.1 - 2025-08-15 - First release with changelog. ================================================ FILE: embassy-boot-rp/Cargo.toml ================================================ [package] edition = "2024" name = "embassy-boot-rp" version = "0.10.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] build = [ {target = "thumbv6m-none-eabi", features = ["embassy-rp/rp2040"]}, ] [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 = "1.0.1", optional = true } log = { version = "0.4", optional = true } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-rp = { version = "0.10.0", path = "../embassy-rp", default-features = false } embassy-boot = { version = "0.7.0", path = "../embassy-boot" } embassy-time = { version = "0.5.1", 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 ================================================ FILE: embassy-boot-rp/README.md ================================================ # 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. ================================================ FILE: embassy-boot-rp/build.rs ================================================ 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)"); } ================================================ FILE: embassy-boot-rp/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-boot-rp/src/lib.rs ================================================ #![no_std] #![allow(unsafe_op_in_unsafe_fn)] #![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::Peri; use embassy_rp::flash::{Blocking, ERASE_SIZE, Flash}; 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 { /// 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) } } /// 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, timeout: Duration, } 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: Peri<'static, FLASH>, watchdog: Peri<'static, WATCHDOG>, timeout: Duration) -> Self { let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); let mut watchdog = Watchdog::new(watchdog); watchdog.start(timeout); Self { flash, watchdog, timeout, } } } 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.timeout); self.flash.blocking_erase(from, to) } fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { self.watchdog.feed(self.timeout); 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.timeout); self.flash.blocking_read(offset, data) } fn capacity(&self) -> usize { self.flash.capacity() } } ================================================ FILE: embassy-boot-stm32/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.8.0 - 2026-03-10 - Update embassy-sync to 0.8.0 - Update embassy-stm32 to 0.6.0 - Update embassy-boot to 0.7.0 ## 0.7.0 - 2026-01-04 - Update embassy-stm32 version ## 0.6.0 - 2025-08-26 ## 0.1.1 - 2025-08-15 - First release with changelog. ================================================ FILE: embassy-boot-stm32/Cargo.toml ================================================ [package] edition = "2024" name = "embassy-boot-stm32" version = "0.8.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] build = [ {target = "thumbv7em-none-eabi", features = ["embassy-stm32/stm32l496zg"]}, ] [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 = "1.0.1", optional = true } log = { version = "0.4", optional = true } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-stm32 = { version = "0.6.0", path = "../embassy-stm32", default-features = false } embassy-boot = { version = "0.7.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 ================================================ FILE: embassy-boot-stm32/README.md ================================================ # 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. ================================================ FILE: embassy-boot-stm32/build.rs ================================================ 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)"); } ================================================ FILE: embassy-boot-stm32/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-boot-stm32/src/lib.rs ================================================ #![no_std] #![allow(unsafe_op_in_unsafe_fn)] #![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) } } ================================================ FILE: embassy-embedded-hal/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.6.0 - 2026-03-10 - Shared I2c busses now impl `Clone` - Upgrade embassy-sync to 0.8.0 ## 0.5.0 - 2025-08-27 ## 0.4.0 - 2025-08-03 - `SpiDevice` cancel safety: always set CS pin to high on drop - Update `embassy-sync` to v0.7.0 ## 0.3.2 - 2025-08-03 - Reverted changes in 0.3.1 - Reexport `SetConfig`, `GetConfig` traits from v0.4.0. ## 0.3.1 - 2025-07-16 YANKED due to embassy-sync upgrade being a breaking change. - `SpiDevice` cancel safety: always set CS pin to high on drop - Update `embassy-sync` to v0.7.0 ## 0.3.0 - 2025-01-05 - The `std` feature has been removed - Updated `embassy-time` to v0.4 ## 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 ================================================ FILE: embassy-embedded-hal/Cargo.toml ================================================ [package] name = "embassy-embedded-hal" version = "0.6.0" edition = "2024" 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] build = [ {target = "thumbv7em-none-eabi", features = []}, {target = "thumbv7em-none-eabi", features = ["time"]}, ] [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/" target = "x86_64-unknown-linux-gnu" [features] defmt = ["dep:defmt"] time = ["dep:embassy-time"] [dependencies] embassy-hal-internal = { version = "0.5.0", path = "../embassy-hal-internal" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-time = { version = "0.5.1", 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 = "1.0.1", optional = true } [dev-dependencies] critical-section = { version = "1.1.1", features = ["std"] } futures-test = "0.3.17" ================================================ FILE: embassy-embedded-hal/README.md ================================================ # 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. ================================================ FILE: embassy-embedded-hal/src/adapter/blocking_async.rs ================================================ /// 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: embedded_hal_1::i2c::I2c, { type Error = E; } impl embedded_hal_async::i2c::I2c for BlockingAsync where E: embedded_hal_1::i2c::Error + 'static, T: embedded_hal_1::i2c::I2c, { 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> { self.wrapped.transaction(address, operations) } } // // SPI implementatinos // impl embedded_hal_async::spi::ErrorType for BlockingAsync where E: embedded_hal_async::spi::Error, T: embedded_hal_1::spi::SpiBus, { type Error = E; } impl embedded_hal_async::spi::SpiBus for BlockingAsync where E: embedded_hal_async::spi::Error, T: embedded_hal_1::spi::SpiBus, { 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.read(data)?; Ok(()) } async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { self.wrapped.transfer(read, write)?; Ok(()) } async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { self.wrapped.transfer_in_place(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 {} ================================================ FILE: embassy-embedded-hal/src/adapter/mod.rs ================================================ //! Adapters between embedded-hal traits. mod blocking_async; mod yielding_async; pub use blocking_async::BlockingAsync; pub use yielding_async::YieldingAsync; ================================================ FILE: embassy-embedded-hal/src/adapter/yielding_async.rs ================================================ use embassy_futures::yield_now; /// Wrapper that yields for each operation to the wrapped instance /// /// This can be used in combination with [super::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(()) } } impl embedded_storage_async::nor_flash::MultiwriteNorFlash for YieldingAsync { } #[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]); } } ================================================ FILE: embassy-embedded-hal/src/flash/concat_flash.rs ================================================ use embedded_storage::nor_flash::{ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, ReadNorFlash}; use embedded_storage_async::nor_flash::{ MultiwriteNorFlash as AsyncMultiwriteNorFlash, 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 MultiwriteNorFlash for ConcatFlash where First: MultiwriteNorFlash, Second: MultiwriteNorFlash, E: NorFlashError, { } 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(()) } } impl AsyncMultiwriteNorFlash for ConcatFlash where First: AsyncMultiwriteNorFlash, Second: AsyncMultiwriteNorFlash, E: NorFlashError, { } #[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]); } } ================================================ FILE: embassy-embedded-hal/src/flash/mem_flash.rs ================================================ 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(()) } } ================================================ FILE: embassy-embedded-hal/src/flash/mod.rs ================================================ //! Utilities related to flash. mod concat_flash; #[cfg(test)] pub(crate) mod mem_flash; pub mod partition; pub use concat_flash::ConcatFlash; ================================================ FILE: embassy-embedded-hal/src/flash/partition/asynch.rs ================================================ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_storage::nor_flash::ErrorType; use embedded_storage_async::nor_flash::{MultiwriteNorFlash, 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) } } impl MultiwriteNorFlash for Partition<'_, M, T> {} #[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().any(|&x| x != 0xAA)); } #[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().any(|&x| x != 0xAA)); } #[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().any(|&x| x != 0xFF)); } } ================================================ FILE: embassy-embedded-hal/src/flash/partition/blocking.rs ================================================ use core::cell::RefCell; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_storage::nor_flash::{ErrorType, MultiwriteNorFlash, 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) }) } } impl MultiwriteNorFlash for BlockingPartition<'_, M, T> {} #[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().any(|&x| x != 0xAA)); } #[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().any(|&x| x != 0xAA)); } #[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().any(|&x| x != 0xFF)); } } ================================================ FILE: embassy-embedded-hal/src/flash/partition/mod.rs ================================================ //! 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(), } } } ================================================ FILE: embassy-embedded-hal/src/lib.rs ================================================ #![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; } ================================================ FILE: embassy-embedded-hal/src/shared_bus/asynch/i2c.rs ================================================ //! 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::SetConfig; use crate::shared_bus::I2cDeviceError; /// 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> Clone for I2cDevice<'a, M, BUS> { fn clone(&self) -> Self { Self { bus: 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, BUS: i2c::I2c, { 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: RawMutex, BUS: SetConfig> Clone for I2cDeviceWithConfig<'a, M, BUS> where BUS::Config: Clone, { fn clone(&self) -> Self { Self { bus: self.bus, config: self.config.clone(), } } } 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, BUS: i2c::I2c + SetConfig, { 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(()) } } ================================================ FILE: embassy-embedded-hal/src/shared_bus/asynch/mod.rs ================================================ //! Asynchronous shared bus implementations for embedded-hal-async pub mod i2c; pub mod spi; ================================================ FILE: embassy-embedded-hal/src/shared_bus/asynch/spi.rs ================================================ //! 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_hal_internal::drop::OnDrop; 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::SetConfig; use crate::shared_bus::SpiDeviceError; /// 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 cs_drop = OnDrop::new(|| { // This drop guard deasserts CS pin if the async operation is cancelled. // Errors are ignored in this drop handler, as there's nothing we can do about them. // If the async operation is completed without cancellation, this handler will not // be run, and the CS pin will be deasserted with proper error handling. let _ = self.cs.set_high(); }); 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; // Now that all the async operations are done, we defuse the CS guard, // and manually set the CS pin low (to better handle the possible errors). cs_drop.defuse(); let cs_res = self.cs.set_high(); op_res.map_err(SpiDeviceError::Spi)?; flush_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; Ok(()) } } /// 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 cs_drop = OnDrop::new(|| { // Please see comment in SpiDevice for an explanation of this drop handler. let _ = self.cs.set_high(); }); 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; cs_drop.defuse(); let cs_res = self.cs.set_high(); op_res.map_err(SpiDeviceError::Spi)?; flush_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; Ok(()) } } ================================================ FILE: embassy-embedded-hal/src/shared_bus/blocking/i2c.rs ================================================ //! 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::Mutex; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_hal_1::i2c::{ErrorType, I2c, Operation}; use crate::SetConfig; use crate::shared_bus::I2cDeviceError; /// 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> Clone for I2cDevice<'a, M, BUS> { fn clone(&self) -> Self { Self { bus: 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 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(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { self.bus .lock(|bus| bus.borrow_mut().write(addr, bytes).map_err(I2cDeviceError::I2c)) } } impl 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(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Self::Error> { self.bus .lock(|bus| bus.borrow_mut().read(addr, bytes).map_err(I2cDeviceError::I2c)) } } impl 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: RawMutex, BUS: SetConfig> Clone for I2cDeviceWithConfig<'a, M, BUS> where BUS::Config: Clone, { fn clone(&self) -> Self { Self { bus: self.bus, config: self.config.clone(), } } } 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) }) } } ================================================ FILE: embassy-embedded-hal/src/shared_bus/blocking/mod.rs ================================================ //! Blocking shared bus implementations for embedded-hal pub mod i2c; pub mod spi; ================================================ FILE: embassy-embedded-hal/src/shared_bus/blocking/spi.rs ================================================ //! 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::Mutex; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_hal_1::digital::OutputPin; use embedded_hal_1::spi::{self, Operation, SpiBus}; use crate::SetConfig; use crate::shared_bus::SpiDeviceError; /// 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(); op_res.map_err(SpiDeviceError::Spi)?; flush_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; Ok(()) }) } } /// 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(); op_res.map_err(SpiDeviceError::Spi)?; flush_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; Ok(()) }) } } ================================================ FILE: embassy-embedded-hal/src/shared_bus/mod.rs ================================================ //! 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, } } } ================================================ FILE: embassy-executor/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.10.0 - 2026-03-10 - Added new metadata API for tasks. - Main task automatically gets a name of `main` when the `metadata-name` feature is enabled. - Upgraded rtos-trace - Added optional "highest priority" scheduling - Added optional "earliest deadline first" EDF scheduling - Migrate `cortex-ar` to `aarch32-cpu`. The feature name `arch-cortex-ar` remains the same and legacy ARM architectures are not supported. - Added `run_until` to `arch-std` variant of `Executor`. - Added `__try_embassy_time_queue_item_from_waker` - Added fallible `from_waker` getter for `TaskRef` - Changed: return error when creating the `SpawnToken`, not when spawning - Bump avr-device from 0.7.0 to 0.8.1 ## 0.9.1 - 2025-08-31 - Fixed performance regression on some ESP32 MCUs. ## 0.9.0 - 2025-08-26 - Added `extern "Rust" fn __embassy_time_queue_item_from_waker` - Removed `TaskRef::dangling` - Added `embassy-executor-timer-queue` as a dependency - Moved the `TimeQueueItem` struct and `timer-item-payload-size-*` features (as `timer-item-size-X-words`) into `embassy-executor-timer-queue` ## 0.8.0 - 2025-07-31 - Added `SpawnToken::id` - Task pools are now statically allocated on stable rust. All `task-arena-size-*` features have been removed and are no longer necessary. - New trace hooks: `_embassy_trace_poll_start` & `_embassy_trace_task_end` - Added task naming capability to tracing infrastructure - Added `Executor::id` & `Spawner::executor_id` - Disable `critical-section/std` for arch-std - Added possibility to select an executor in `#[embassy_executor::main]` - Fix AVR executor - executor: Make state implementations and their conditions match - Added support for Cortex-A and Cortex-R - Added support for `-> impl Future` in `#[task]` - Fixed `Send` unsoundness with `-> impl Future` tasks - Marked `Spawner::for_current_executor` as `unsafe` - `#[task]` now properly marks the generated function as unsafe if the task is marked unsafe ## 0.7.0 - 2025-01-02 - Performance optimizations. - Remove feature `integrated-timers`. Starting with `embassy-time-driver` v0.2, `embassy-time` v0.4 the timer queue is now part of the time driver, so it's no longer the executor's responsibility. Therefore, `embassy-executor` no longer provides an `embassy-time-queue-driver` implementation. - Added the possibility for timer driver implementations to store arbitrary data in task headers. This can be used to make a timer queue intrusive list, similar to the previous `integrated-timers` feature. Payload size is controlled by the `timer-item-payload-size-X` features. - Added `TaskRef::executor` to obtain a reference to a task's executor ## 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 ================================================ FILE: embassy-executor/Cargo.toml ================================================ [package] name = "embassy-executor" version = "0.10.0" edition = "2024" 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] build = [ {target = "thumbv7em-none-eabi", features = []}, {target = "thumbv7em-none-eabi", features = ["log"]}, {target = "thumbv7em-none-eabi", features = ["defmt"]}, {target = "thumbv6m-none-eabi", features = ["defmt"]}, {target = "thumbv6m-none-eabi", features = ["platform-cortex-m", "defmt", "executor-interrupt", "executor-thread"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "rtos-trace"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-thread"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority", "scheduler-deadline"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-deadline"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "scheduler-priority", "scheduler-deadline"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "scheduler-deadline"]}, {target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority", "scheduler-deadline", "trace"]}, {target = "thumbv7em-none-eabi", features = ["platform-spin"]}, {target = "thumbv7em-none-eabi", features = ["platform-spin", "scheduler-deadline"]}, {target = "armv7a-none-eabi", features = ["platform-cortex-ar", "executor-thread"]}, {target = "armv7r-none-eabi", features = ["platform-cortex-ar", "executor-thread"]}, {target = "armv7r-none-eabihf", features = ["platform-cortex-ar", "executor-thread"]}, {target = "riscv32imac-unknown-none-elf", features = ["platform-riscv32"]}, {target = "riscv32imac-unknown-none-elf", features = ["platform-riscv32", "executor-thread"]}, {target = "riscv32imac-unknown-none-elf", features = ["platform-riscv32", "executor-thread", "trace"]}, # Nightly builds {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly"]}, {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "log"]}, {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "defmt"]}, {group = "nightly", target = "thumbv6m-none-eabi", features = ["nightly", "defmt"]}, {group = "nightly", target = "thumbv6m-none-eabi", features = ["nightly", "defmt", "platform-cortex-m", "executor-thread", "executor-interrupt"]}, {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "platform-cortex-m"]}, {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "platform-cortex-m", "executor-thread"]}, {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "platform-cortex-m", "executor-interrupt"]}, {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "platform-cortex-m", "executor-thread", "executor-interrupt"]}, {group = "nightly", target = "riscv32imac-unknown-none-elf", features = ["nightly", "platform-riscv32"]}, {group = "nightly", target = "riscv32imac-unknown-none-elf", features = ["nightly", "platform-riscv32", "executor-thread"]}, {group = "nightly", target = "armv7a-none-eabi", features = ["nightly", "platform-cortex-ar", "executor-thread"]}, {group = "nightly", target = "avr-none", features = ["nightly", "platform-avr", "avr-device/atmega328p"], build-std = ["core", "alloc"], env = { RUSTFLAGS = "-Ctarget-cpu=atmega328p" }}, # Xtensa builds {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = []}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["log"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s2-none-elf", features = ["defmt"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "platform-spin", "executor-thread"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s2-none-elf", features = ["defmt", "platform-spin", "executor-thread"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s3-none-elf", features = ["defmt", "platform-spin", "executor-thread"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["platform-spin"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["platform-spin", "rtos-trace"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["platform-spin", "executor-thread"]}, ] [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", "scheduler-deadline", "scheduler-priority"] flavors = [ { name = "std", target = "x86_64-unknown-linux-gnu", features = ["platform-std", "executor-thread"] }, { name = "wasm", target = "wasm32-unknown-unknown", features = ["platform-wasm", "executor-thread"] }, { name = "cortex-m", target = "thumbv7em-none-eabi", features = ["platform-cortex-m", "executor-thread", "executor-interrupt"] }, { name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["platform-riscv32", "executor-thread"] }, ] [package.metadata.docs.rs] default-target = "thumbv7em-none-eabi" targets = ["thumbv7em-none-eabi"] features = ["defmt", "platform-cortex-m", "executor-thread", "executor-interrupt", "scheduler-deadline", "scheduler-priority", "embassy-time-driver"] [dependencies] defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } rtos-trace = { version = "0.2", optional = true } embassy-executor-macros = { version = "0.8.0", path = "../embassy-executor-macros" } embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", optional = true } embassy-executor-timer-queue = { version = "0.1", path = "../embassy-executor-timer-queue" } critical-section = "1.1" document-features = "0.2.7" # needed for AVR portable-atomic = { version = "1.5", optional = true } # platform-cortex-m dependencies cortex-m = { version = "0.7.6", optional = true } # platform-cortex-ar dependencies aarch32-cpu = { version = "0.1", optional = true } # platform-wasm dependencies wasm-bindgen = { version = "0.2.82", optional = true } js-sys = { version = "0.3", optional = true } # platform-avr dependencies avr-device = { version = "0.8.1", optional = true } [dependencies.cordyceps] version = "0.3.4" features = ["no-cache-pad"] [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } trybuild = "1.0" embassy-sync = { path = "../embassy-sync" } rustversion = "1.0.21" [features] ## Enable nightly-only features nightly = ["embassy-executor-macros/nightly"] ## Enable defmt logging defmt = ["dep:defmt"] ## Enable log logging log = ["dep:log"] # 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 = [] #! ### Platform _platform = [] # some platform was picked ## STD platform. Enables running the executor on top of `std` threading primitives. platform-std = ["_platform"] ## Cortex-M platform. Uses `WFE`/`SEV` for the thread executor, NVIC interrupts for the interrupt executor. ## ## - Only usable on **single-core chips**. (Exception: the thread executor might work if your chip has wired the "event" signal between the cores, so `SEV` on one core can break a `WFE` in the other) ## - Only usable on **bare-metal**. Do not use this if you want to run the executor inside an RTOS thread, you must use RTOS primitives to sleep/notify the thread instead. ## - Only usable on **privileged mode**. platform-cortex-m = ["_platform", "dep:cortex-m"] ## Cortex-A/R platform. Uses `WFE`/`SEV` for the thread executor. Interrupt executor is not supported. ## ## - Only usable on **single-core chips**. (Exception: the thread executor might work if your chip has wired the "event" signal between the cores, so `SEV` on one core can break a `WFE` in the other) ## - Only usable on **bare-metal**. Do not use this if you want to run the executor inside an RTOS thread, you must use RTOS primitives to sleep/notify the thread instead. ## - Only usable on **privileged mode**. platform-cortex-ar = ["_platform", "dep:aarch32-cpu", "dep:arm-targets"] ## RISC-V 32-bit platform. Uses WFI for the thread executor. Interrupt executor is not supported. ## ## - Only usable on **single-core chips**. ## - Only usable on **bare-metal**. Do not use this if you want to run the executor inside an RTOS thread, you must use RTOS primitives to sleep/notify the thread instead. ## - Only usable on **privileged mode**. platform-riscv32 = ["_platform"] ## WASM platform. platform-wasm = ["_platform", "dep:wasm-bindgen", "dep:js-sys"] ## AVR platform. Uses WFI for the thread executor. Interrupt executor is not supported. platform-avr = ["_platform", "dep:portable-atomic", "dep:avr-device"] ## Spin-loop platform. ## ## This "platform" implementation is architecture/platform/chip agnostic. The main loop polls the executor constantly without sleeping, and the pender callback simply does nothing. Using this is not recommended, you probably want to use a platform-specific implementation that can sleep instead. platform-spin = ["_platform"] #! ### Metadata ## Enable the `name` field in task metadata. metadata-name = ["embassy-executor-macros/metadata-name"] #! ### Executor ## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded platforms) executor-thread = [] ## Enable the interrupt-mode executor (available in Cortex-M only) executor-interrupt = [] ## Enable tracing hooks trace = ["_any_trace"] ## Enable support for rtos-trace framework rtos-trace = ["_any_trace", "metadata-name", "dep:rtos-trace", "embassy-time-driver"] _any_trace = [] ## Enable "Earliest Deadline First" Scheduler, using soft-realtime "deadlines" to prioritize ## tasks based on the remaining time before their deadline. Adds some overhead. scheduler-deadline = [] ## Enable "Highest Priority First" Scheduler. Adds some overhead. scheduler-priority = [] ## Enable the embassy_time_driver dependency. ## This can unlock extra APIs, for example for the `scheduler-deadline` embassy-time-driver = ["dep:embassy-time-driver"] [build-dependencies] arm-targets = { version = "0.4", optional = true } ================================================ FILE: embassy-executor/README.md ================================================ # embassy-executor An async/await executor designed for embedded usage. - No `alloc`, no heap needed. - Tasks are statically allocated. 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. - 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. ## Platforms The executor requires a "platform" to be defined to work. A platform defines the following things: - The main loop, which typically consists of an infinite loop of polling the executor then sleeping the current thread/core in a platform-specific way. - A "pender" callback, which must cause the executor's thread/core to exit sleep so the executor gets polled again. This is called when a task running in the executor is woken. The `embassy-executor` crate ships with support for some commonly used platforms, see the crate's feature documentation. Chip-specific executor platform implementations are maintained in their respective HALs: - `embassy-rp`: multicore support. Enabled with the `executor-interrupt` or `executor-thread` features. - `embassy-stm32`: automatic low-power sleep support. Enabled with the `executor-interrupt` or `executor-thread` features. - `embassy-mcxa`: automatic low-power sleep support. Enabled with the `executor-platform` feature. - `esp-rtos`: ESP32 RTOS support, multicore support. Enabled with the `embassy` feature. To use the executor, you must provide exactly one platform implementation, either from this crate, a HAL crate, or a custom one. ## Implementing a custom platform To implement your own custom platform, e.g. on top of an RTOS, do the following: 1. define the `__pender` callback. ```rust,ignore #[unsafe(export_name = "__pender")] fn __pender(context: *mut ()) { // `context` is the argument passed to `raw::Executor::new`. Here we're using it // to pass a handle to the RTOS task but you can use it for anything. my_rtos::notify_task(context as _); } ``` 2. Wrap the `raw::Executor` into your own `Executor` struct that defines the main loop. ```rust,ignore pub struct Executor { inner: raw::Executor, not_send: PhantomData<*mut ()>, } impl Executor { pub fn new() -> Self { Self { inner: raw::Executor::new(my_rtos::task_get_current() as _), not_send: PhantomData, } } pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { init(self.inner.spawner()); loop { unsafe { self.inner.poll() } my_rtos::task_wait_for_notification(); } } } ``` ================================================ FILE: embassy-executor/build.rs ================================================ #[path = "./build_common.rs"] mod common; fn main() { let mut rustc_cfgs = common::CfgSet::new(); common::set_target_cfgs(&mut rustc_cfgs); // This is used to exclude legacy architecture support. The raw executor needs to be used for // those architectures because SEV/WFE are not supported. #[cfg(feature = "platform-cortex-ar")] arm_targets::process(); } ================================================ FILE: embassy-executor/build_common.rs ================================================ // 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")); } ================================================ FILE: embassy-executor/gen_config.py ================================================ 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) ================================================ FILE: embassy-executor/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-executor/src/lib.rs ================================================ #![cfg_attr(not(any(feature = "platform-std", feature = "platform-wasm")), no_std)] #![allow(clippy::new_without_default)] #![allow(unsafe_op_in_unsafe_fn)] #![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!( "platform-avr", "platform-cortex-m", "platform-cortex-ar", "platform-riscv32", "platform-std", "platform-wasm", "platform-spin", ); #[cfg(feature = "_platform")] #[cfg_attr(feature = "platform-avr", path = "platform/avr.rs")] #[cfg_attr(feature = "platform-cortex-m", path = "platform/cortex_m.rs")] #[cfg_attr(feature = "platform-cortex-ar", path = "platform/cortex_ar.rs")] #[cfg_attr(feature = "platform-riscv32", path = "platform/riscv32.rs")] #[cfg_attr(feature = "platform-std", path = "platform/std.rs")] #[cfg_attr(feature = "platform-wasm", path = "platform/wasm.rs")] #[cfg_attr(feature = "platform-spin", path = "platform/spin.rs")] mod platform; #[cfg(not(feature = "_platform"))] pub use embassy_executor_macros::main_unspecified as main; #[cfg(feature = "_platform")] #[allow(unused_imports)] // don't warn if the module is empty. pub use platform::*; pub mod raw; mod spawner; pub use spawner::*; mod metadata; pub use metadata::*; /// 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::cell::UnsafeCell; use core::future::Future; use core::mem::MaybeUninit; use crate::raw::TaskPool; trait TaskReturnValue {} impl TaskReturnValue for () {} impl TaskReturnValue for Never {} #[diagnostic::on_unimplemented( message = "task futures must resolve to `()` or `!`", note = "use `async fn` or change the return type to `impl Future`" )] #[allow(private_bounds)] pub trait TaskFn: Copy { type Fut: Future + 'static; } macro_rules! task_fn_impl { ($($Tn:ident),*) => { impl TaskFn<($($Tn,)*)> for F where F: Copy + FnOnce($($Tn,)*) -> Fut, Fut: Future + 'static, { type Fut = Fut; } }; } task_fn_impl!(); task_fn_impl!(T0); task_fn_impl!(T0, T1); task_fn_impl!(T0, T1, T2); task_fn_impl!(T0, T1, T2, T3); task_fn_impl!(T0, T1, T2, T3, T4); task_fn_impl!(T0, T1, T2, T3, T4, T5); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); #[allow(private_bounds)] #[repr(C)] pub struct TaskPoolHolder where Align: Alignment, { data: UnsafeCell<[MaybeUninit; SIZE]>, align: Align, } unsafe impl Send for TaskPoolHolder where Align: Alignment {} unsafe impl Sync for TaskPoolHolder where Align: Alignment {} #[allow(private_bounds)] impl TaskPoolHolder where Align: Alignment, { pub const fn get(&self) -> *const u8 { self.data.get().cast() } } pub const fn task_pool_size(_: F) -> usize where F: TaskFn, Fut: Future + 'static, { size_of::>() } pub const fn task_pool_align(_: F) -> usize where F: TaskFn, Fut: Future + 'static, { align_of::>() } pub const fn task_pool_new(_: F) -> TaskPool where F: TaskFn, Fut: Future + 'static, { TaskPool::new() } #[allow(private_bounds)] #[repr(transparent)] pub struct Align([::Archetype; 0]) where Self: Alignment; trait Alignment { /// A zero-sized type of particular alignment. type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin; } macro_rules! aligns { ($($AlignX:ident: $n:literal,)*) => { $( #[derive(Copy, Clone, Eq, PartialEq)] #[repr(align($n))] struct $AlignX {} impl Alignment for Align<$n> { type Archetype = $AlignX; } )* }; } aligns!( Align1: 1, Align2: 2, Align4: 4, Align8: 8, Align16: 16, Align32: 32, Align64: 64, Align128: 128, Align256: 256, Align512: 512, Align1024: 1024, Align2048: 2048, Align4096: 4096, Align8192: 8192, Align16384: 16384, ); #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] aligns!( Align32768: 32768, Align65536: 65536, Align131072: 131072, Align262144: 262144, Align524288: 524288, Align1048576: 1048576, Align2097152: 2097152, Align4194304: 4194304, Align8388608: 8388608, Align16777216: 16777216, Align33554432: 33554432, Align67108864: 67108864, Align134217728: 134217728, Align268435456: 268435456, Align536870912: 536870912, ); #[allow(dead_code)] pub trait HasOutput { type Output; } impl HasOutput for fn() -> O { type Output = O; } #[allow(dead_code)] pub type Never = ! as HasOutput>::Output; } /// Implementation details for embassy macros. /// Do not use. Used for macros and HALs only. Not covered by semver guarantees. #[doc(hidden)] #[cfg(feature = "nightly")] pub mod _export { #[diagnostic::on_unimplemented( message = "task futures must resolve to `()` or `!`", note = "use `async fn` or change the return type to `impl Future`" )] pub trait TaskReturnValue {} impl TaskReturnValue for () {} impl TaskReturnValue for Never {} #[allow(dead_code)] pub trait HasOutput { type Output; } impl HasOutput for fn() -> O { type Output = O; } #[allow(dead_code)] pub type Never = ! as HasOutput>::Output; } ================================================ FILE: embassy-executor/src/metadata.rs ================================================ #[cfg(feature = "metadata-name")] use core::cell::Cell; use core::future::{Future, poll_fn}; #[cfg(feature = "scheduler-priority")] use core::sync::atomic::{AtomicU8, Ordering}; use core::task::Poll; #[cfg(feature = "metadata-name")] use critical_section::Mutex; use crate::raw; #[cfg(feature = "scheduler-deadline")] use crate::raw::Deadline; /// Metadata associated with a task. pub struct Metadata { #[cfg(feature = "metadata-name")] name: Mutex>>, #[cfg(feature = "scheduler-priority")] priority: AtomicU8, #[cfg(feature = "scheduler-deadline")] deadline: raw::Deadline, } impl Metadata { pub(crate) const fn new() -> Self { Self { #[cfg(feature = "metadata-name")] name: Mutex::new(Cell::new(None)), #[cfg(feature = "scheduler-priority")] priority: AtomicU8::new(0), // NOTE: The deadline is set to zero to allow the initializer to reside in `.bss`. This // will be lazily initalized in `initialize_impl` #[cfg(feature = "scheduler-deadline")] deadline: raw::Deadline::new_unset(), } } pub(crate) fn reset(&self) { #[cfg(feature = "metadata-name")] critical_section::with(|cs| self.name.borrow(cs).set(None)); #[cfg(feature = "scheduler-priority")] self.set_priority(0); // By default, deadlines are set to the maximum value, so that any task WITH // a set deadline will ALWAYS be scheduled BEFORE a task WITHOUT a set deadline #[cfg(feature = "scheduler-deadline")] self.unset_deadline(); } /// Get the metadata for the current task. /// /// You can use this to read or modify the current task's metadata. /// /// This function is `async` just to get access to the current async /// context. It returns instantly, it does not block/yield. pub fn for_current_task() -> impl Future { poll_fn(|cx| Poll::Ready(raw::task_from_waker(cx.waker()).metadata())) } /// Get this task's name /// /// NOTE: this takes a critical section. #[cfg(feature = "metadata-name")] pub fn name(&self) -> Option<&'static str> { critical_section::with(|cs| self.name.borrow(cs).get()) } /// Set this task's name /// /// NOTE: this takes a critical section. #[cfg(feature = "metadata-name")] pub fn set_name(&self, name: &'static str) { critical_section::with(|cs| self.name.borrow(cs).set(Some(name))) } /// Get this task's priority. #[cfg(feature = "scheduler-priority")] pub fn priority(&self) -> u8 { self.priority.load(Ordering::Relaxed) } /// Set this task's priority. #[cfg(feature = "scheduler-priority")] pub fn set_priority(&self, priority: u8) { self.priority.store(priority, Ordering::Relaxed) } /// Get this task's deadline. #[cfg(feature = "scheduler-deadline")] pub fn deadline(&self) -> u64 { self.deadline.instant_ticks() } /// Set this task's deadline. /// /// This method does NOT check whether the deadline has already passed. #[cfg(feature = "scheduler-deadline")] pub fn set_deadline(&self, instant_ticks: u64) { self.deadline.set(instant_ticks); } /// Remove this task's deadline. /// This brings it back to the defaul where it's not scheduled ahead of other tasks. #[cfg(feature = "scheduler-deadline")] pub fn unset_deadline(&self) { self.deadline.set(Deadline::UNSET_TICKS); } /// Set this task's deadline `duration_ticks` in the future from when /// this future is polled. This deadline is saturated to the max tick value. /// /// Analogous to `Timer::after`. #[cfg(all(feature = "scheduler-deadline", feature = "embassy-time-driver"))] pub fn set_deadline_after(&self, duration_ticks: u64) { let now = embassy_time_driver::now(); // Since ticks is a u64, saturating add is PROBABLY overly cautious, leave // it for now, we can probably make this wrapping_add for performance // reasons later. let deadline = now.saturating_add(duration_ticks); self.set_deadline(deadline); } /// Set the this task's deadline `increment_ticks` from the previous deadline. /// /// This deadline is saturated to the max tick value. /// /// Note that by default (unless otherwise set), tasks start life with the deadline /// not set, which means this method will have no effect. /// /// Analogous to one increment of `Ticker::every().next()`. /// /// Returns the deadline that was set. #[cfg(feature = "scheduler-deadline")] pub fn increment_deadline(&self, duration_ticks: u64) { let last = self.deadline(); // Since ticks is a u64, saturating add is PROBABLY overly cautious, leave // it for now, we can probably make this wrapping_add for performance // reasons later. let deadline = last.saturating_add(duration_ticks); self.set_deadline(deadline); } } ================================================ FILE: embassy-executor/src/platform/avr.rs ================================================ #[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::{Spawner, raw}; static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); #[unsafe(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(); } } } } } } ================================================ FILE: embassy-executor/src/platform/cortex_ar.rs ================================================ #[cfg(arm_profile = "legacy")] compile_error!("`arch-cortex-ar` does not support the legacy ARM profile, WFE/SEV are not available."); #[cfg(feature = "executor-interrupt")] compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`."); #[unsafe(export_name = "__pender")] #[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] fn __pender(context: *mut ()) { // `context` is always `usize::MAX` created by `Executor::run`. 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 { aarch32_cpu::asm::sev(); return; } } #[cfg(feature = "executor-thread")] pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { pub(super) const THREAD_PENDER: usize = usize::MAX; use core::marker::PhantomData; use aarch32_cpu::asm::wfe; pub use embassy_executor_macros::main_cortex_ar as main; use crate::{Spawner, raw}; /// 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(); } wfe(); } } } } ================================================ FILE: embassy-executor/src/platform/cortex_m.rs ================================================ #[unsafe(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::{Spawner, raw}; /// 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`](crate::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`](crate::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`](crate::Spawner) for this executor, use [`Spawner::for_current_executor()`](crate::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. /// /// [`SendSpawner`]: crate::SendSpawner 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`](crate::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() } } } ================================================ FILE: embassy-executor/src/platform/riscv32.rs ================================================ #[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::{Spawner, raw}; /// 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); #[unsafe(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 } } } } } ================================================ FILE: embassy-executor/src/platform/spin.rs ================================================ #[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::{Spawner, raw}; #[unsafe(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() }; } } } } ================================================ FILE: embassy-executor/src/platform/std.rs ================================================ #[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::{Spawner, raw}; #[unsafe(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)) -> ! { self.run_until(init, || false); unreachable!() } /// Run the executor until a flag is raised. /// /// This function is identical to `Executor::run()` apart from offering a `done` flag to stop execution. pub fn run_until(&'static mut self, init: impl FnOnce(Spawner), mut done: impl FnMut() -> bool) { init(self.inner.spawner()); loop { unsafe { self.inner.poll() }; if done() { break; } 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(); } } } ================================================ FILE: embassy-executor/src/platform/wasm.rs ================================================ #[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::{Spawner, raw}; #[unsafe(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()); } } } } ================================================ FILE: embassy-executor/src/raw/deadline.rs ================================================ use core::sync::atomic::{AtomicU32, Ordering}; /// A type for interacting with the deadline of the current task /// /// Requires the `scheduler-deadline` feature. /// /// Note: Interacting with the deadline should be done locally in a task. /// In theory you could try to set or read the deadline from another task, /// but that will result in weird (though not unsound) behavior. pub(crate) struct Deadline { instant_ticks_hi: AtomicU32, instant_ticks_lo: AtomicU32, } impl Deadline { pub(crate) const fn new(instant_ticks: u64) -> Self { Self { instant_ticks_hi: AtomicU32::new((instant_ticks >> 32) as u32), instant_ticks_lo: AtomicU32::new(instant_ticks as u32), } } pub(crate) const fn new_unset() -> Self { Self::new(Self::UNSET_TICKS) } pub(crate) fn set(&self, instant_ticks: u64) { self.instant_ticks_hi .store((instant_ticks >> 32) as u32, Ordering::Relaxed); self.instant_ticks_lo.store(instant_ticks as u32, Ordering::Relaxed); } /// Deadline value in ticks, same time base and ticks as `embassy-time` pub(crate) fn instant_ticks(&self) -> u64 { let hi = self.instant_ticks_hi.load(Ordering::Relaxed) as u64; let lo = self.instant_ticks_lo.load(Ordering::Relaxed) as u64; (hi << 32) | lo } /// Sentinel value representing an "unset" deadline, which has lower priority /// than any other set deadline value pub(crate) const UNSET_TICKS: u64 = u64::MAX; } ================================================ FILE: embassy-executor/src/raw/mod.rs ================================================ //! 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. mod run_queue; #[cfg_attr(all(cortex_m, target_has_atomic = "32"), path = "state_atomics_arm.rs")] #[cfg_attr( all(not(cortex_m), any(target_has_atomic = "8", target_has_atomic = "32")), path = "state_atomics.rs" )] #[cfg_attr( not(any(target_has_atomic = "8", target_has_atomic = "32")), path = "state_critical_section.rs" )] mod state; #[cfg(feature = "_any_trace")] pub mod trace; pub(crate) mod util; #[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] mod waker; #[cfg(feature = "scheduler-deadline")] mod deadline; use core::future::Future; use core::marker::PhantomData; use core::mem; use core::pin::Pin; use core::ptr::NonNull; #[cfg(not(feature = "platform-avr"))] use core::sync::atomic::AtomicPtr; use core::sync::atomic::Ordering; use core::task::{Context, Poll, Waker}; #[cfg(feature = "scheduler-deadline")] pub(crate) use deadline::Deadline; use embassy_executor_timer_queue::TimerQueueItem; #[cfg(feature = "platform-avr")] use portable_atomic::AtomicPtr; use self::run_queue::{RunQueue, RunQueueItem}; use self::state::State; use self::util::{SyncUnsafeCell, UninitCell}; pub use self::waker::task_from_waker; use self::waker::try_task_from_waker; use super::SpawnToken; use crate::{Metadata, SpawnError}; #[unsafe(no_mangle)] extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static mut TimerQueueItem { unsafe { task_from_waker(waker).timer_queue_item() } } #[unsafe(no_mangle)] extern "Rust" fn __try_embassy_time_queue_item_from_waker(waker: &Waker) -> Option<&'static mut TimerQueueItem> { unsafe { try_task_from_waker(waker).map(|task| task.timer_queue_item()) } } /// 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: TimerQueueItem, pub(crate) metadata: Metadata, #[cfg(feature = "rtos-trace")] all_tasks_next: AtomicPtr, } /// This is essentially a `&'static TaskStorage` where the type of the future has been erased. #[derive(Debug, 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), } } pub(crate) fn header(self) -> &'static TaskHeader { unsafe { self.ptr.as_ref() } } pub(crate) fn metadata(self) -> &'static Metadata { unsafe { &self.ptr.as_ref().metadata } } /// 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 mutable reference to the timer queue item. /// /// Safety /// /// This function must only be called in the context of the integrated timer queue. pub unsafe fn timer_queue_item(mut self) -> &'static mut TimerQueueItem { unsafe { &mut self.ptr.as_mut().timer_queue_item } } /// The returned pointer is valid for the entire TaskStorage. pub(crate) fn as_ptr(self) -> *const TaskHeader { self.ptr.as_ptr() } /// Returns the task ID. /// This can be used in combination with rtos-trace to match task names with IDs pub fn id(&self) -> u32 { self.as_ptr() as u32 } } /// 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: TimerQueueItem::new(), metadata: Metadata::new(), #[cfg(feature = "rtos-trace")] all_tasks_next: AtomicPtr::new(core::ptr::null_mut()), }, 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) -> Result, SpawnError> { let task = AvailableTask::claim(self); match task { Some(task) => Ok(task.initialize(future)), None => Err(SpawnError::Busy), } } 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(_) => { #[cfg(feature = "_any_trace")] let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed); // 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(); #[cfg(feature = "_any_trace")] trace::task_end(exec_ptr, &p); } 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.metadata.reset(); 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) -> Result, SpawnError> { match self.pool.iter().find_map(AvailableTask::claim) { Some(task) => Ok(task.initialize_impl::(future)), None => Err(SpawnError::Busy), } } /// 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) -> Result, SpawnError> { 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) -> Result, SpawnError> 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) { unsafe 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 = "_any_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 = "_any_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) { #[cfg(feature = "_any_trace")] trace::poll_start(self); self.run_queue.dequeue_all(|p| { let task = p.header(); #[cfg(feature = "_any_trace")] trace::task_exec_begin(self, &p); // Run the task task.poll_fn.get().unwrap_unchecked()(p); #[cfg(feature = "_any_trace")] trace::task_exec_end(self, &p); }); #[cfg(feature = "_any_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 /// #[unsafe(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 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) } /// Get a unique ID for this Executor. pub fn id(&'static self) -> usize { &self.inner as *const SyncExecutor as usize } } /// 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); } }); } ================================================ FILE: embassy-executor/src/raw/run_queue.rs ================================================ use core::ptr::{NonNull, addr_of_mut}; use cordyceps::Linked; #[cfg(any(feature = "scheduler-priority", feature = "scheduler-deadline"))] use cordyceps::SortedList; use cordyceps::sorted_list::Links; #[cfg(target_has_atomic = "ptr")] type TransferStack = cordyceps::TransferStack; #[cfg(not(target_has_atomic = "ptr"))] type TransferStack = MutexTransferStack; use super::{TaskHeader, TaskRef}; /// Use `cordyceps::sorted_list::Links` as the singly linked list /// for RunQueueItems. pub(crate) type RunQueueItem = Links; /// Implements the `Linked` trait, allowing for singly linked list usage /// of any of cordyceps' `TransferStack` (used for the atomic runqueue), /// `SortedList` (used with the DRS scheduler), or `Stack`, which is /// popped atomically from the `TransferStack`. unsafe impl Linked> for TaskHeader { type Handle = TaskRef; // Convert a TaskRef into a TaskHeader ptr fn into_ptr(r: TaskRef) -> NonNull { r.ptr } // Convert a TaskHeader into a TaskRef unsafe fn from_ptr(ptr: NonNull) -> TaskRef { TaskRef { ptr } } // Given a pointer to a TaskHeader, obtain a pointer to the Links structure, // which can be used to traverse to other TaskHeader nodes in the linked list unsafe fn links(ptr: NonNull) -> NonNull> { let ptr: *mut TaskHeader = ptr.as_ptr(); NonNull::new_unchecked(addr_of_mut!((*ptr).run_queue_item)) } } /// 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 { stack: TransferStack, } impl RunQueue { pub const fn new() -> Self { Self { stack: TransferStack::new(), } } /// 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, _tok: super::state::Token) -> bool { self.stack.push_was_empty( task, #[cfg(not(target_has_atomic = "ptr"))] _tok, ) } /// # Standard atomic runqueue /// /// 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. #[cfg(not(any(feature = "scheduler-priority", feature = "scheduler-deadline")))] pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { let taken = self.stack.take_all(); for taskref in taken { run_dequeue(&taskref); on_task(taskref); } } /// # Earliest Deadline First Scheduler /// /// This algorithm will loop until all enqueued tasks are processed. /// /// Before polling a task, all currently enqueued tasks will be popped from the /// runqueue, and will be added to the working `sorted` list, a linked-list that /// sorts tasks by their deadline, with nearest deadline items in the front, and /// furthest deadline items in the back. /// /// After popping and sorting all pending tasks, the SOONEST task will be popped /// from the front of the queue, and polled by calling `on_task` on it. /// /// This process will repeat until the local `sorted` queue AND the global /// runqueue are both empty, at which point this function will return. #[cfg(any(feature = "scheduler-priority", feature = "scheduler-deadline"))] pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { let mut sorted = SortedList::::new_with_cmp(|lhs, rhs| { // compare by priority first #[cfg(feature = "scheduler-priority")] { let lp = lhs.metadata.priority(); let rp = rhs.metadata.priority(); if lp != rp { return lp.cmp(&rp).reverse(); } } // compare deadlines in case of tie. #[cfg(feature = "scheduler-deadline")] { let ld = lhs.metadata.deadline(); let rd = rhs.metadata.deadline(); if ld != rd { return ld.cmp(&rd); } } core::cmp::Ordering::Equal }); loop { // For each loop, grab any newly pended items let taken = self.stack.take_all(); // Sort these into the list - this is potentially expensive! We do an // insertion sort of new items, which iterates the linked list. // // Something on the order of `O(n * m)`, where `n` is the number // of new tasks, and `m` is the number of already pending tasks. sorted.extend(taken); // Pop the task with the SOONEST deadline. If there are no tasks // pending, then we are done. let Some(taskref) = sorted.pop_front() else { return; }; // We got one task, mark it as dequeued, and process the task. run_dequeue(&taskref); on_task(taskref); } } } /// atomic state does not require a cs... #[cfg(target_has_atomic = "ptr")] #[inline(always)] fn run_dequeue(taskref: &TaskRef) { taskref.header().state.run_dequeue(); } /// ...while non-atomic state does #[cfg(not(target_has_atomic = "ptr"))] #[inline(always)] fn run_dequeue(taskref: &TaskRef) { critical_section::with(|cs| { taskref.header().state.run_dequeue(cs); }) } /// A wrapper type that acts like TransferStack by wrapping a normal Stack in a CS mutex #[cfg(not(target_has_atomic = "ptr"))] struct MutexTransferStack>> { inner: critical_section::Mutex>>, } #[cfg(not(target_has_atomic = "ptr"))] impl>> MutexTransferStack { const fn new() -> Self { Self { inner: critical_section::Mutex::new(core::cell::UnsafeCell::new(cordyceps::Stack::new())), } } /// Push an item to the transfer stack, returning whether the stack was previously empty fn push_was_empty(&self, item: T::Handle, token: super::state::Token) -> bool { // SAFETY: The critical-section mutex guarantees that there is no *concurrent* access // for the lifetime of the token, but does NOT protect against re-entrant access. // However, we never *return* the reference, nor do we recurse (or call another method // like `take_all`) that could ever allow for re-entrant aliasing. Therefore, the // presence of the critical section is sufficient to guarantee exclusive access to // the `inner` field for the purposes of this function. let inner = unsafe { &mut *self.inner.borrow(token).get() }; let is_empty = inner.is_empty(); inner.push(item); is_empty } fn take_all(&self) -> cordyceps::Stack { critical_section::with(|cs| { // SAFETY: The critical-section mutex guarantees that there is no *concurrent* access // for the lifetime of the token, but does NOT protect against re-entrant access. // However, we never *return* the reference, nor do we recurse (or call another method // like `push_was_empty`) that could ever allow for re-entrant aliasing. Therefore, the // presence of the critical section is sufficient to guarantee exclusive access to // the `inner` field for the purposes of this function. let inner = unsafe { &mut *self.inner.borrow(cs).get() }; inner.take_all() }) } } ================================================ FILE: embassy-executor/src/raw/state_atomics.rs ================================================ // Prefer pointer-width atomic operations, as narrower ones may be slower. #[cfg(all(target_pointer_width = "32", target_has_atomic = "32"))] type AtomicState = core::sync::atomic::AtomicU32; #[cfg(not(all(target_pointer_width = "32", target_has_atomic = "32")))] type AtomicState = core::sync::atomic::AtomicU8; #[cfg(all(target_pointer_width = "32", target_has_atomic = "32"))] type StateBits = u32; #[cfg(not(all(target_pointer_width = "32", target_has_atomic = "32")))] type StateBits = u8; use core::sync::atomic::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: StateBits = 1 << 0; /// Task is in the executor run queue pub(crate) const STATE_RUN_QUEUED: StateBits = 1 << 1; pub(crate) struct State { state: AtomicState, } impl State { pub const fn new() -> State { Self { state: AtomicState::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); } } ================================================ FILE: embassy-executor/src/raw/state_atomics_arm.rs ================================================ use core::sync::atomic::{AtomicBool, AtomicU32, Ordering, compiler_fence}; #[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); } } ================================================ FILE: embassy-executor/src/raw/state_critical_section.rs ================================================ use core::cell::Cell; use critical_section::{CriticalSection, Mutex}; pub(crate) use critical_section::{CriticalSection as Token, with as locked}; #[cfg(target_arch = "avr")] type StateBits = u8; #[cfg(not(target_arch = "avr"))] type StateBits = usize; /// Task is spawned (has a future) pub(crate) const STATE_SPAWNED: StateBits = 1 << 0; /// Task is in the executor run queue pub(crate) const STATE_RUN_QUEUED: StateBits = 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 StateBits) -> R) -> R { critical_section::with(|cs| self.update_with_cs(cs, f)) } fn update_with_cs(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut StateBits) -> 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) } } ================================================ FILE: embassy-executor/src/raw/trace.rs ================================================ //! # Tracing //! //! The `trace` feature enables a number of callbacks that can be used to track the //! lifecycle of tasks and/or executors. //! //! Callbacks will have one or both of the following IDs passed to them: //! //! 1. A `task_id`, a `u32` value unique to a task for the duration of the time it is valid //! 2. An `executor_id`, a `u32` value unique to an executor for the duration of the time it is //! valid //! //! Today, both `task_id` and `executor_id` are u32s containing the least significant 32 bits of //! the address of the task or executor, however this is NOT a stable guarantee, and MAY change //! at any time. //! //! IDs are only guaranteed to be unique for the duration of time the item is valid. If a task //! ends, and is re-spawned, it MAY or MAY NOT have the same ID. For tasks, this valid time is defined //! as the time between `_embassy_trace_task_new` and `_embassy_trace_task_end` for a given task. //! For executors, this time is not defined, but is often "forever" for practical embedded //! programs. //! //! Callbacks can be used by enabling the `trace` feature, and providing implementations of the //! `extern "Rust"` functions below. All callbacks must be implemented. //! //! ## Task Tracing lifecycle //! //! ```text //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ //! │(1) │ //! │ │ //! ╔════▼════╗ (2) ┌─────────┐ (3) ┌─────────┐ │ //! │ ║ SPAWNED ║────▶│ WAITING │────▶│ RUNNING │ //! ╚═════════╝ └─────────┘ └─────────┘ │ //! │ ▲ ▲ │ │ │ //! │ (4) │ │(6) │ //! │ │(7) └ ─ ─ ┘ │ │ //! │ │ │ │ //! │ ┌──────┐ (5) │ │ ┌─────┐ //! │ IDLE │◀────────────────┘ └─▶│ END │ │ //! │ └──────┘ └─────┘ //! ┌──────────────────────┐ │ //! └ ┤ Task Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ //! └──────────────────────┘ //! ``` //! //! 1. A task is spawned, `_embassy_trace_task_new` is called //! 2. A task is enqueued for the first time, `_embassy_trace_task_ready_begin` is called //! 3. A task is polled, `_embassy_trace_task_exec_begin` is called //! 4. WHILE a task is polled, the task is re-awoken, and `_embassy_trace_task_ready_begin` is //! called. The task does not IMMEDIATELY move state, until polling is complete and the //! RUNNING state is existed. `_embassy_trace_task_exec_end` is called when polling is //! complete, marking the transition to WAITING //! 5. Polling is complete, `_embassy_trace_task_exec_end` is called //! 6. The task has completed, and `_embassy_trace_task_end` is called //! 7. A task is awoken, `_embassy_trace_task_ready_begin` is called //! //! ## Executor Tracing lifecycle //! //! ```text //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ //! │(1) │ //! │ │ //! ╔═══▼══╗ (2) ┌────────────┐ (3) ┌─────────┐ │ //! │ ║ IDLE ║──────────▶│ SCHEDULING │──────▶│ POLLING │ //! ╚══════╝ └────────────┘ └─────────┘ │ //! │ ▲ │ ▲ │ //! │ (5) │ │ (4) │ │ //! │ └──────────────┘ └────────────┘ //! ┌──────────────────────────┐ │ //! └ ┤ Executor Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ //! └──────────────────────────┘ //! ``` //! //! 1. The executor is started (no associated trace) //! 2. A task on this executor is awoken. `_embassy_trace_task_ready_begin` is called //! when this occurs, and `_embassy_trace_poll_start` is called when the executor //! actually begins running //! 3. The executor has decided a task to poll. `_embassy_trace_task_exec_begin` is called //! 4. The executor finishes polling the task. `_embassy_trace_task_exec_end` is called //! 5. The executor has finished polling tasks. `_embassy_trace_executor_idle` is called #![allow(unused)] use core::cell::UnsafeCell; use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; #[cfg(feature = "rtos-trace")] use rtos_trace::TaskInfo; use crate::raw::{SyncExecutor, TaskHeader, TaskRef}; use crate::spawner::{SpawnError, SpawnToken, Spawner}; /// Global task tracker instance /// /// This static provides access to the global task tracker which maintains /// a list of all tasks in the system. It's automatically updated by the /// task lifecycle hooks in the trace module. #[cfg(feature = "rtos-trace")] pub(crate) static TASK_TRACKER: TaskTracker = TaskTracker::new(); /// A thread-safe tracker for all tasks in the system /// /// This struct uses an intrusive linked list approach to track all tasks /// without additional memory allocations. It maintains a global list of /// tasks that can be traversed to find all currently existing tasks. #[cfg(feature = "rtos-trace")] pub(crate) struct TaskTracker { head: AtomicPtr, } #[cfg(feature = "rtos-trace")] impl TaskTracker { /// Creates a new empty task tracker /// /// Initializes a tracker with no tasks in its list. pub const fn new() -> Self { Self { head: AtomicPtr::new(core::ptr::null_mut()), } } /// Adds a task to the tracker /// /// This method inserts a task at the head of the intrusive linked list. /// The operation is thread-safe and lock-free, using atomic operations /// to ensure consistency even when called from different contexts. /// /// # Arguments /// * `task` - The task reference to add to the tracker pub fn add(&self, task: TaskRef) { let task_ptr = task.as_ptr(); loop { let current_head = self.head.load(Ordering::Acquire); unsafe { (*task_ptr).all_tasks_next.store(current_head, Ordering::Relaxed); } if self .head .compare_exchange(current_head, task_ptr.cast_mut(), Ordering::Release, Ordering::Relaxed) .is_ok() { break; } } } /// Performs an operation on each task in the tracker /// /// This method traverses the entire list of tasks and calls the provided /// function for each task. This allows inspecting or processing all tasks /// in the system without modifying the tracker's structure. /// /// # Arguments /// * `f` - A function to call for each task in the tracker pub fn for_each(&self, mut f: F) where F: FnMut(TaskRef), { let mut current = self.head.load(Ordering::Acquire); while !current.is_null() { let task = unsafe { TaskRef::from_ptr(current) }; f(task); current = unsafe { (*current).all_tasks_next.load(Ordering::Acquire) }; } } } #[cfg(feature = "trace")] unsafe extern "Rust" { /// This callback is called when the executor begins polling. This will always /// be paired with a later call to `_embassy_trace_executor_idle`. /// /// This marks the EXECUTOR state transition from IDLE -> SCHEDULING. fn _embassy_trace_poll_start(executor_id: u32); /// This callback is called AFTER a task is initialized/allocated, and BEFORE /// it is enqueued to run for the first time. If the task ends (and does not /// loop "forever"), there will be a matching call to `_embassy_trace_task_end`. /// /// Tasks start life in the SPAWNED state. fn _embassy_trace_task_new(executor_id: u32, task_id: u32); /// This callback is called AFTER a task is destructed/freed. This will always /// have a prior matching call to `_embassy_trace_task_new`. fn _embassy_trace_task_end(executor_id: u32, task_id: u32); /// This callback is called AFTER a task has been dequeued from the runqueue, /// and BEFORE the task is polled. There will always be a matching call to /// `_embassy_trace_task_exec_end`. /// /// This marks the TASK state transition from WAITING -> RUNNING /// This marks the EXECUTOR state transition from SCHEDULING -> POLLING fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); /// This callback is called AFTER a task has completed polling. There will /// always be a matching call to `_embassy_trace_task_exec_begin`. /// /// This marks the TASK state transition from either: /// * RUNNING -> IDLE - if there were no `_embassy_trace_task_ready_begin` events /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task /// * RUNNING -> WAITING - if there WAS a `_embassy_trace_task_ready_begin` event /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task /// /// This marks the EXECUTOR state transition from POLLING -> SCHEDULING fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); /// This callback is called AFTER the waker for a task is awoken, and BEFORE it /// is added to the run queue. /// /// If the given task is currently RUNNING, this marks no state change, BUT the /// RUNNING task will then move to the WAITING stage when polling is complete. /// /// If the given task is currently IDLE, this marks the TASK state transition /// from IDLE -> WAITING. /// /// NOTE: This may be called from an interrupt, outside the context of the current /// task or executor. fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); /// This callback is called AFTER all dequeued tasks in a single call to poll /// have been processed. This will always be paired with a call to /// `_embassy_trace_executor_idle`. /// /// This marks the EXECUTOR state transition from SCHEDULING -> IDLE fn _embassy_trace_executor_idle(executor_id: u32); } #[inline] pub(crate) fn poll_start(executor: &SyncExecutor) { #[cfg(feature = "trace")] unsafe { _embassy_trace_poll_start(executor as *const _ as u32) } } #[inline] pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { #[cfg(feature = "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); let name = task.metadata().name().unwrap_or("unnamed task\0"); let info = rtos_trace::TaskInfo { name, priority: 0, stack_base: 0, stack_size: 0, }; rtos_trace::trace::task_send_info(task.id(), info); } #[cfg(feature = "rtos-trace")] TASK_TRACKER.add(*task); } #[inline] pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) { #[cfg(feature = "trace")] unsafe { _embassy_trace_task_end(executor as u32, task.as_ptr() as u32) } } #[inline] pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { #[cfg(feature = "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(feature = "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(feature = "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(feature = "trace")] unsafe { _embassy_trace_executor_idle(executor as *const _ as u32) } #[cfg(feature = "rtos-trace")] rtos_trace::trace::system_idle(); } /// Returns an iterator over all active tasks in the system /// /// This function provides a convenient way to iterate over all tasks /// that are currently tracked in the system. The returned iterator /// yields each task in the global task tracker. /// /// # Returns /// An iterator that yields `TaskRef` items for each task #[cfg(feature = "rtos-trace")] fn get_all_active_tasks() -> impl Iterator + 'static { struct TaskIterator<'a> { tracker: &'a TaskTracker, current: *mut TaskHeader, } impl<'a> Iterator for TaskIterator<'a> { type Item = TaskRef; fn next(&mut self) -> Option { if self.current.is_null() { return None; } let task = unsafe { TaskRef::from_ptr(self.current) }; self.current = unsafe { (*self.current).all_tasks_next.load(Ordering::Acquire) }; Some(task) } } TaskIterator { tracker: &TASK_TRACKER, current: TASK_TRACKER.head.load(Ordering::Acquire), } } /// Perform an action on each active task #[cfg(feature = "rtos-trace")] fn with_all_active_tasks(f: F) where F: FnMut(TaskRef), { TASK_TRACKER.for_each(f); } #[cfg(feature = "rtos-trace")] impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { fn task_list() { with_all_active_tasks(|task| { let info = rtos_trace::TaskInfo { name: task.metadata().name().unwrap_or("unnamed task\0"), priority: 0, stack_base: 0, stack_size: 0, }; rtos_trace::trace::task_send_info(task.id(), info); }); } 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} ================================================ FILE: embassy-executor/src/raw/util.rs ================================================ 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() } } ================================================ FILE: embassy-executor/src/raw/waker.rs ================================================ use core::task::{RawWaker, RawWakerVTable, Waker}; use super::{TaskHeader, TaskRef, wake_task}; 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`]. /// /// # Panics /// /// Panics if the waker is not created by the Embassy executor. pub fn task_from_waker(waker: &Waker) -> TaskRef { unwrap!( try_task_from_waker(waker), "Found waker not created by the Embassy executor. Unless the generic timer queue is enabled, `embassy_time::Timer` only works with the Embassy executor." ) } pub(crate) fn try_task_from_waker(waker: &Waker) -> Option { // 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 _ { return None; } // safety: our wakers are always created with `TaskRef::as_ptr` Some(unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) }) } ================================================ FILE: embassy-executor/src/raw/waker_turbo.rs ================================================ use core::ptr::NonNull; use core::task::Waker; use super::{TaskHeader, TaskRef, wake_task}; 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) } } pub(crate) fn try_task_from_waker(waker: &Waker) -> Option { Some(task_from_waker(waker)) } #[inline(never)] #[unsafe(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) } ================================================ FILE: embassy-executor/src/spawner.rs ================================================ use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::mem; use core::sync::atomic::Ordering; use core::task::Poll; use super::raw; use crate::Metadata; /// 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 { pub(crate) raw_task: raw::TaskRef, phantom: PhantomData<*mut S>, } impl SpawnToken { pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { Self { raw_task, phantom: PhantomData, } } /// Returns the task ID. /// This can be used in combination with rtos-trace to match task names with IDs pub fn id(&self) -> u32 { self.raw_task.id() } /// Get the metadata for this task. You can use this to set metadata fields /// prior to spawning it. pub fn metadata(&self) -> &Metadata { self.raw_task.metadata() } } 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)] 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, } impl core::fmt::Debug for SpawnError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Display::fmt(self, f) } } impl core::fmt::Display for SpawnError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { SpawnError::Busy => write!( f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task." ), } } } #[cfg(feature = "defmt")] impl defmt::Format for SpawnError { fn format(&self, f: defmt::Formatter) { match self { SpawnError::Busy => defmt::write!( f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task." ), } } } impl core::error::Error for SpawnError {} /// 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 { pub(crate) 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. /// /// Using this method is discouraged due to it being unsafe. Consider the following /// alternatives instead: /// /// - Pass the initial `Spawner` as an argument to tasks. Note that it's `Copy`, so you can /// make as many copies of it as you want. /// - Use `SendSpawner::for_current_executor()` instead, which is safe but can only be used /// if task arguments are `Send`. /// /// The only case where using this method is absolutely required is obtaining the `Spawner` /// for an `InterruptExecutor`. /// /// # Safety /// /// You must only execute this with an async `Context` created by the Embassy executor. /// You must not execute it with manually-created `Context`s. /// /// # Panics /// /// Panics if the current executor is not an Embassy executor. pub unsafe fn for_current_executor() -> impl Future { 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)) }) } /// 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) { let task = token.raw_task; mem::forget(token); unsafe { self.executor.spawn(task) } } /// 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) } /// Return the unique ID of this Spawner's Executor. pub fn executor_id(&self) -> usize { self.executor.id() } } /// 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 fn for_current_executor() -> impl Future { 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)) }) } /// 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) { let header = token.raw_task; mem::forget(token); unsafe { self.executor.spawn(header) } } } ================================================ FILE: embassy-executor/tests/test.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #![cfg_attr(feature = "nightly", feature(never_type))] use std::boxed::Box; use std::future::{Future, poll_fn}; use std::sync::{Arc, Mutex}; use std::task::Poll; use embassy_executor::raw::Executor; use embassy_executor::{Spawner, task}; #[unsafe(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") } #[task] async fn task2() -> ! { panic!() } 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_rpit() { #[task] fn task1(trace: Trace) -> impl Future { async move { trace.push("poll task1") } } #[cfg(feature = "nightly")] #[task] fn task2() -> impl Future { async { panic!() } } 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); } } #[test] fn recursive_task() { #[embassy_executor::task(pool_size = 2)] async fn task1() { let spawner = unsafe { Spawner::for_current_executor().await }; spawner.spawn(task1().unwrap()); } } #[cfg(feature = "metadata-name")] #[test] fn task_metadata() { #[task] async fn task1(expected_name: Option<&'static str>) { use embassy_executor::Metadata; assert_eq!(Metadata::for_current_task().await.name(), expected_name); } // check no task name let (executor, _) = setup(); executor.spawner().spawn(task1(None).unwrap()); unsafe { executor.poll() }; // check setting task name let token = task1(Some("foo")).unwrap(); token.metadata().set_name("foo"); executor.spawner().spawn(token); unsafe { executor.poll() }; let token = task1(Some("bar")).unwrap(); token.metadata().set_name("bar"); executor.spawner().spawn(token); unsafe { executor.poll() }; // check name is cleared if the task pool slot is recycled. let (executor, _) = setup(); executor.spawner().spawn(task1(None).unwrap()); unsafe { executor.poll() }; } ================================================ FILE: embassy-executor/tests/ui/abi.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async extern "C" fn task() {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/abi.stderr ================================================ error: task functions must not have an ABI qualifier --> tests/ui/abi.rs:6:1 | 6 | async extern "C" fn task() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/bad_return.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task() -> u32 { 5 } fn main() {} ================================================ FILE: embassy-executor/tests/ui/bad_return.stderr ================================================ error: task functions must either not return a value, return `()` or return `!` --> tests/ui/bad_return.rs:6:1 | 6 | async fn task() -> u32 { | ^^^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/bad_return_impl_future.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] use core::future::Future; #[embassy_executor::task] fn task() -> impl Future { async { 5 } } fn main() {} ================================================ FILE: embassy-executor/tests/ui/bad_return_impl_future.stderr ================================================ error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:5:4 | 4 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 5 | fn task() -> impl Future { | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_size` --> src/lib.rs | | pub const fn task_pool_size(_: F) -> usize | -------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^ required by this bound in `task_pool_size` error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:4:1 | 4 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_size` --> src/lib.rs | | pub const fn task_pool_size(_: F) -> usize | -------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_size` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:5:4 | 4 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 5 | fn task() -> impl Future { | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_align` --> src/lib.rs | | pub const fn task_pool_align(_: F) -> usize | --------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^ required by this bound in `task_pool_align` error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:4:1 | 4 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_align` --> src/lib.rs | | pub const fn task_pool_align(_: F) -> usize | --------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_align` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:5:4 | 4 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 5 | fn task() -> impl Future { | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `__task_pool_get` --> tests/ui/bad_return_impl_future.rs:4:1 | 4 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__task_pool_get` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:5:4 | 4 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 5 | fn task() -> impl Future { | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_new` --> src/lib.rs | | pub const fn task_pool_new(_: F) -> TaskPool | ------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^ required by this bound in `task_pool_new` error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future.rs:4:1 | 4 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_new` --> src/lib.rs | | pub const fn task_pool_new(_: F) -> TaskPool | ------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_new` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: embassy-executor/tests/ui/bad_return_impl_future_nightly.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] use core::future::Future; #[embassy_executor::task] fn task() -> impl Future { async { 5 } } fn main() {} ================================================ FILE: embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr ================================================ error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/bad_return_impl_future_nightly.rs:4:1 | 4 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskReturnValue` is not implemented for `u32` | = note: use `async fn` or change the return type to `impl Future` help: the following other types implement trait `TaskReturnValue` --> src/lib.rs | | impl TaskReturnValue for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `()` | impl TaskReturnValue for Never {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ` ! as HasOutput>::Output` ================================================ FILE: embassy-executor/tests/ui/generics.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task(_t: T) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/generics.stderr ================================================ error: task functions must not be generic --> tests/ui/generics.rs:6:1 | 6 | async fn task(_t: T) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/impl_trait.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn foo(_x: impl Sized) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/impl_trait.stderr ================================================ 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) {} | ^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/impl_trait_nested.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo(T); #[embassy_executor::task] async fn foo(_x: Foo) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/impl_trait_nested.stderr ================================================ 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) {} | ^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/impl_trait_static.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn foo(_x: impl Sized + 'static) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/impl_trait_static.stderr ================================================ 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) {} | ^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_anon.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn foo(_x: &'_ u32) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_anon.stderr ================================================ 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) {} | ^^ ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn foo(_x: &'static &'_ u32) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr ================================================ 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) {} | ^^ ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_elided.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn foo(_x: &u32) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_elided.stderr ================================================ 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) {} | ^ ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_generic.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn foo<'a>(_x: &'a u32) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_ref_generic.stderr ================================================ 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) {} | ^^ ================================================ FILE: embassy-executor/tests/ui/nonstatic_struct_anon.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task(_x: Foo<'_>) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_struct_anon.stderr ================================================ 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<'_>) {} | ^^ ================================================ FILE: embassy-executor/tests/ui/nonstatic_struct_elided.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task(_x: Foo) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_struct_elided.stderr ================================================ 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<'_>) {} | ++++ error: lifetime may not live long enough --> tests/ui/nonstatic_struct_elided.rs:5:1 | 5 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static` 6 | async fn task(_x: Foo) {} | -- has type `Foo<'1>` | = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) help: to declare that `impl Sized` captures data from argument `_x`, you can add an explicit `'_` lifetime bound | 5 | #[embassy_executor::task] + '_ | ++++ ================================================ FILE: embassy-executor/tests/ui/nonstatic_struct_generic.rs ================================================ #![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() {} ================================================ FILE: embassy-executor/tests/ui/nonstatic_struct_generic.stderr ================================================ 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>) {} | ^^ ================================================ FILE: embassy-executor/tests/ui/not_async.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] fn task() {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/not_async.stderr ================================================ error: task functions must be async --> tests/ui/not_async.rs:6:1 | 6 | fn task() {} | ^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/return_impl_future_nonsend.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] use core::future::Future; use embassy_executor::SendSpawner; #[embassy_executor::task] fn task() -> impl Future { // runs in spawning thread let non_send: *mut () = core::ptr::null_mut(); async move { // runs in executor thread println!("{}", non_send as usize); } } fn send_spawn(s: SendSpawner) { s.spawn(task().unwrap()); } fn main() {} ================================================ FILE: embassy-executor/tests/ui/return_impl_future_nonsend.stderr ================================================ error: future cannot be sent between threads safely --> tests/ui/return_impl_future_nonsend.rs:18:13 | 18 | s.spawn(task().unwrap()); | ^^^^^^^^^^^^^^^ future created by async block is not `Send` | = help: within `impl Sized`, the trait `Send` is not implemented for `*mut ()` note: captured value is not `Send` --> tests/ui/return_impl_future_nonsend.rs:13:24 | 13 | println!("{}", non_send as usize); | ^^^^^^^^ has type `*mut ()` which is not `Send` note: required by a bound in `SendSpawner::spawn` --> src/spawner.rs | | pub fn spawn(&self, token: SpawnToken) { | ^^^^ required by this bound in `SendSpawner::spawn` ================================================ FILE: embassy-executor/tests/ui/return_impl_send.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] fn task() -> impl Send {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/return_impl_send.stderr ================================================ error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:4:4 | 3 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 4 | fn task() -> impl Send {} | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_size` --> src/lib.rs | | pub const fn task_pool_size(_: F) -> usize | -------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^ required by this bound in `task_pool_size` error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:3:1 | 3 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_size` --> src/lib.rs | | pub const fn task_pool_size(_: F) -> usize | -------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_size` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:4:4 | 3 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 4 | fn task() -> impl Send {} | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_align` --> src/lib.rs | | pub const fn task_pool_align(_: F) -> usize | --------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^ required by this bound in `task_pool_align` error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:3:1 | 3 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_align` --> src/lib.rs | | pub const fn task_pool_align(_: F) -> usize | --------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_align` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:4:4 | 3 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 4 | fn task() -> impl Send {} | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `__task_pool_get` --> tests/ui/return_impl_send.rs:3:1 | 3 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__task_pool_get` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `impl Send` is not a future --> tests/ui/return_impl_send.rs:3:1 | 3 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ `impl Send` is not a future | = help: the trait `Future` is not implemented for `impl Send` note: required by a bound in `TaskPool::::spawn` --> src/raw/mod.rs | | impl TaskPool { | ^^^^^^ required by this bound in `TaskPool::::spawn` ... | pub fn spawn(&'static self, future: impl FnOnce() -> F) -> Result, SpawnError> { | ----- required by a bound in this associated function = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:4:4 | 3 | #[embassy_executor::task] | ------------------------- required by a bound introduced by this call 4 | fn task() -> impl Send {} | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_new` --> src/lib.rs | | pub const fn task_pool_new(_: F) -> TaskPool | ------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^ required by this bound in `task_pool_new` error[E0277]: task futures must resolve to `()` or `!` --> tests/ui/return_impl_send.rs:3:1 | 3 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}` | = note: use `async fn` or change the return type to `impl Future` note: required by a bound in `task_pool_new` --> src/lib.rs | | pub const fn task_pool_new(_: F) -> TaskPool | ------------- required by a bound in this function | where | F: TaskFn, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_new` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: embassy-executor/tests/ui/return_impl_send_nightly.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] fn task() -> impl Send {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/return_impl_send_nightly.stderr ================================================ error[E0277]: `impl Send` is not a future --> tests/ui/return_impl_send_nightly.rs:3:1 | 3 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ | | | `impl Send` is not a future | return type was inferred to be `impl Send` here | = help: the trait `Future` is not implemented for `impl Send` ================================================ FILE: embassy-executor/tests/ui/self.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task(self) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/self.stderr ================================================ 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 ================================================ FILE: embassy-executor/tests/ui/self_ref.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task(&mut self) {} fn main() {} ================================================ FILE: embassy-executor/tests/ui/self_ref.stderr ================================================ 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 ================================================ FILE: embassy-executor/tests/ui/spawn_nonsend.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] use embassy_executor::SendSpawner; #[embassy_executor::task] async fn task(non_send: *mut ()) { println!("{}", non_send as usize); } fn send_spawn(s: SendSpawner) { s.spawn(task(core::ptr::null_mut()).unwrap()); } fn main() {} ================================================ FILE: embassy-executor/tests/ui/spawn_nonsend.stderr ================================================ error[E0277]: `*mut ()` cannot be sent between threads safely --> tests/ui/spawn_nonsend.rs:11:13 | 5 | #[embassy_executor::task] | ------------------------- within this `impl Sized` ... 11 | s.spawn(task(core::ptr::null_mut()).unwrap()); | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `*mut ()` cannot be sent between threads safely | | | required by a bound introduced by this call | = help: within `impl Sized`, the trait `Send` is not implemented for `*mut ()` note: required because it's used within this closure --> tests/ui/spawn_nonsend.rs:5:1 | 5 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ note: required because it appears within the type `impl Sized` --> src/raw/mod.rs | | pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> Result, SpawnError> | ^^^^^^^^^^ note: required because it appears within the type `impl Sized` --> tests/ui/spawn_nonsend.rs:5:1 | 5 | #[embassy_executor::task] | ^^^^^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `SendSpawner::spawn` --> src/spawner.rs | | pub fn spawn(&self, token: SpawnToken) { | ^^^^ required by this bound in `SendSpawner::spawn` = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: embassy-executor/tests/ui/task_safety_attribute.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #![deny(unused_unsafe)] use std::mem; #[embassy_executor::task] async fn safe() {} #[embassy_executor::task] async unsafe fn not_safe() {} #[unsafe(export_name = "__pender")] fn pender(_: *mut ()) { // The test doesn't link if we don't include this. // We never call this anyway. } fn main() { let _forget_me = safe(); // SAFETY: not_safe has not safety preconditions let _forget_me2 = unsafe { not_safe() }; mem::forget(_forget_me); mem::forget(_forget_me2); } ================================================ FILE: embassy-executor/tests/ui/type_error.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #[embassy_executor::task] async fn task() { 5 } fn main() {} ================================================ FILE: embassy-executor/tests/ui/type_error.stderr ================================================ error[E0308]: mismatched types --> tests/ui/type_error.rs:5:5 | 4 | async fn task() { | - help: try adding a return type: `-> i32` 5 | 5 | ^ expected `()`, found integer ================================================ FILE: embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] #![deny(unsafe_op_in_unsafe_fn)] #[embassy_executor::task] async unsafe fn task() { let x = 5; (&x as *const i32).read(); } fn main() {} ================================================ FILE: embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr ================================================ error[E0133]: call to unsafe function `std::ptr::const_ptr::::read` is unsafe and requires unsafe block --> tests/ui/unsafe_op_in_unsafe_task.rs:7:5 | 7 | (&x as *const i32).read(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function | = note: for more information, see = note: consult the function's documentation for information on how to avoid undefined behavior note: an unsafe function restricts its caller, but its body is safe by default --> tests/ui/unsafe_op_in_unsafe_task.rs:5:1 | 5 | async unsafe fn task() { | ^^^^^^^^^^^^^^^^^^^^^^ note: the lint level is defined here --> tests/ui/unsafe_op_in_unsafe_task.rs:2:9 | 2 | #![deny(unsafe_op_in_unsafe_fn)] | ^^^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: embassy-executor/tests/ui/where_clause.rs ================================================ #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] struct Foo<'a>(&'a ()); #[embassy_executor::task] async fn task() where (): Sized, { } fn main() {} ================================================ FILE: embassy-executor/tests/ui/where_clause.stderr ================================================ error: task functions must not have `where` clauses --> tests/ui/where_clause.rs:6:1 | 6 | / async fn task() 7 | | where 8 | | (): Sized, | |______________^ ================================================ FILE: embassy-executor/tests/ui.rs ================================================ #[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/spawn_nonsend.rs"); t.compile_fail("tests/ui/return_impl_future_nonsend.rs"); if rustversion::cfg!(stable) { // output is slightly different on nightly t.compile_fail("tests/ui/bad_return_impl_future.rs"); t.compile_fail("tests/ui/return_impl_send.rs"); } if cfg!(feature = "nightly") { t.compile_fail("tests/ui/bad_return_impl_future_nightly.rs"); t.compile_fail("tests/ui/return_impl_send_nightly.rs"); } t.compile_fail("tests/ui/self_ref.rs"); t.compile_fail("tests/ui/self.rs"); t.compile_fail("tests/ui/type_error.rs"); t.compile_fail("tests/ui/where_clause.rs"); t.compile_fail("tests/ui/unsafe_op_in_unsafe_task.rs"); t.pass("tests/ui/task_safety_attribute.rs"); } ================================================ FILE: embassy-executor-macros/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.8.0 - 2026-03-12 - Update rust release id - Add task name capability - Follow executor api change ================================================ FILE: embassy-executor-macros/Cargo.toml ================================================ [package] name = "embassy-executor-macros" version = "0.8.0" edition = "2024" 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 = [] metadata-name = [] ================================================ FILE: embassy-executor-macros/README.md ================================================ # embassy-executor-macros An [Embassy](https://embassy.dev) project. NOTE: Do not use this crate directly. The macros are re-exported by `embassy-executor`. ================================================ FILE: embassy-executor-macros/src/lib.rs ================================================ #![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 application entry point for Cortex-A/R /// 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_ar(args: TokenStream, item: TokenStream) -> TokenStream { main::run(args.into(), item.into(), &main::ARCH_CORTEX_AR).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() } /// Creates a new `executor` instance and declares an application entry point for an unspecified architecture, 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 and executor type must be provided via the `entry` and `executor` arguments of the `main` macro. /// /// ## Examples /// Spawning a task: /// ``` rust /// #[embassy_executor::main(entry = "your_hal::entry", executor = "your_hal::Executor")] /// async fn main(_s: embassy_executor::Spawner) { /// // Function body /// } /// ``` #[proc_macro_attribute] pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream { main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into() } ================================================ FILE: embassy-executor-macros/src/macros/main.rs ================================================ use std::str::FromStr; use darling::FromMeta; use darling::export::NestedMeta; 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, executor_required: bool, } pub static ARCH_AVR: Arch = Arch { default_entry: Some("avr_device::entry"), flavor: Flavor::Standard, executor_required: false, }; pub static ARCH_RISCV: Arch = Arch { default_entry: Some("riscv_rt::entry"), flavor: Flavor::Standard, executor_required: false, }; pub static ARCH_CORTEX_M: Arch = Arch { default_entry: Some("cortex_m_rt::entry"), flavor: Flavor::Standard, executor_required: false, }; pub static ARCH_CORTEX_AR: Arch = Arch { default_entry: None, flavor: Flavor::Standard, executor_required: false, }; pub static ARCH_SPIN: Arch = Arch { default_entry: None, flavor: Flavor::Standard, executor_required: false, }; pub static ARCH_STD: Arch = Arch { default_entry: None, flavor: Flavor::Standard, executor_required: false, }; pub static ARCH_WASM: Arch = Arch { default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), flavor: Flavor::Wasm, executor_required: false, }; pub static ARCH_UNSPECIFIED: Arch = Arch { default_entry: None, flavor: Flavor::Standard, executor_required: true, }; #[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] entry: Option, #[darling(default)] executor: 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(), arch.default_entry.as_deref()) { (None, None) => TokenStream::new(), (Some(x), _) | (None, Some(x)) if x == "" => TokenStream::new(), (Some(x), _) | (None, Some(x)) => match TokenStream::from_str(x) { Ok(x) => quote!(#[#x]), Err(e) => { error(&mut errors, &f.sig, e); TokenStream::new() } }, }; let executor = match (args.executor.as_deref(), arch.executor_required) { (None, true) => { error( &mut errors, &f.sig, "\ No architecture selected for embassy-executor. Make sure you've enabled one of the `arch-*` features in your Cargo.toml. Alternatively, if you would like to use a custom executor implementation, specify it with the `executor` argument. For example: `#[embassy_executor::main(entry = ..., executor = \"some_crate::Executor\")]", ); "" } (Some(x), _) => x, (None, _) => "::embassy_executor::Executor", }; let executor = TokenStream::from_str(executor).unwrap_or_else(|e| { error(&mut errors, &f.sig, e); TokenStream::new() }); let f_body = f.body; let out = &f.sig.output; let name_main_task = if cfg!(feature = "metadata-name") { quote!( main_task.metadata().set_name("main\0"); ) } else { quote!() }; let (main_ret, mut main_body) = match arch.flavor { Flavor::Standard => ( quote!(!), quote! { unsafe fn __make_static(t: &mut T) -> &'static mut T { unsafe { ::core::mem::transmute(t) } } let mut executor = #executor::new(); let executor = unsafe { __make_static(&mut executor) }; executor.run(|spawner| { let main_task = __embassy_main(spawner).unwrap(); #name_main_task spawner.spawn(main_task); }) }, ), Flavor::Wasm => ( quote!(Result<(), wasm_bindgen::JsValue>), quote! { let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(#executor::new())); executor.start(|spawner| { let main_task = __embassy_main(spawner).unwrap(); #name_main_task spawner.spawn(main_task); }); Ok(()) }, ), }; let mut main_attrs = TokenStream::new(); for attr in f.attrs { main_attrs.extend(quote!(#attr)); } if !errors.is_empty() { main_body = quote! {loop{}}; } let result = quote! { #[::embassy_executor::task()] #[allow(clippy::future_not_send)] async fn __embassy_main(#fargs) #out { #f_body } #entry #main_attrs fn main() -> #main_ret { #main_body } #errors }; result } ================================================ FILE: embassy-executor-macros/src/macros/mod.rs ================================================ pub mod main; pub mod task; ================================================ FILE: embassy-executor-macros/src/macros/task.rs ================================================ use std::str::FromStr; use darling::FromMeta; use darling::export::NestedMeta; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::visit::{self, Visit}; use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type, Visibility}; 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())); let returns_impl_trait = match &f.sig.output { ReturnType::Type(_, ty) => matches!(**ty, Type::ImplTrait(_)), _ => false, }; if f.sig.asyncness.is_none() && !returns_impl_trait { 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"); } if f.sig.asyncness.is_some() { 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", ); } } } } } // Copy the generics + where clause to avoid more spurious errors. let generics = &f.sig.generics; let where_clause = &f.sig.generics.where_clause; let unsafety = &f.sig.unsafety; let visibility = &f.vis; // 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 )); } let task_ident = f.sig.ident.clone(); let task_inner_ident = format_ident!("__{}_task", task_ident); let task_inner_future_output = match &f.sig.output { ReturnType::Default => quote! {-> impl ::core::future::Future}, // Special case the never type since we can't stuff it into a `impl Future` ReturnType::Type(arrow, maybe_never) if f.sig.asyncness.is_some() && matches!(**maybe_never, Type::Never(_)) => { quote! { #arrow impl ::core::future::Future } } ReturnType::Type(arrow, maybe_never) if matches!(**maybe_never, Type::Never(_)) => quote! { #arrow #maybe_never }, // Grab the arrow span, why not ReturnType::Type(arrow, typ) if f.sig.asyncness.is_some() => quote! { #arrow impl ::core::future::Future }, // We assume that if `f` isn't async, it must return `-> impl Future<...>` // This is checked using traits later ReturnType::Type(arrow, typ) => quote! { #arrow #typ }, }; // We have to rename the function since it might be recursive; let mut task_inner_function = f.clone(); let task_inner_function_ident = format_ident!("__{}_task_inner_function", task_ident); task_inner_function.sig.ident = task_inner_function_ident.clone(); task_inner_function.vis = Visibility::Inherited; let task_inner_body = if errors.is_empty() { quote! { #task_inner_function // SAFETY: All the preconditions to `#task_ident` apply to // all contexts `#task_inner_ident` is called in #unsafety { #task_inner_function_ident(#(#full_args,)*) } } } else { quote! { async {::core::todo!()} } }; let task_inner = quote! { #visibility fn #task_inner_ident #generics (#fargs) #task_inner_future_output #where_clause { #task_inner_body } }; let spawn = if returns_impl_trait { quote!(spawn) } else { quote!(_spawn_async_fn) }; #[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(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } }; #[cfg(not(feature = "nightly"))] let mut task_outer_body = quote! { const fn __task_pool_get(_: F) -> &'static #embassy_executor::raw::TaskPool where F: #embassy_executor::_export::TaskFn, Fut: ::core::future::Future + 'static, { unsafe { &*POOL.get().cast() } } const POOL_SIZE: usize = #pool_size; static POOL: #embassy_executor::_export::TaskPoolHolder< {#embassy_executor::_export::task_pool_size::<_, _, _, POOL_SIZE>(#task_inner_ident)}, {#embassy_executor::_export::task_pool_align::<_, _, _, POOL_SIZE>(#task_inner_ident)}, > = unsafe { ::core::mem::transmute(#embassy_executor::_export::task_pool_new::<_, _, _, POOL_SIZE>(#task_inner_ident)) }; unsafe { __task_pool_get(#task_inner_ident).#spawn(move || #task_inner_ident(#(#full_args,)*)) } }; let task_outer_attrs = &f.attrs; if !errors.is_empty() { task_outer_body = quote! { #![allow(unused_variables, unreachable_code)] let _x: ::core::result::Result<#embassy_executor::SpawnToken<()>, #embassy_executor::SpawnError> = ::core::todo!(); _x }; } 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 #unsafety fn #task_ident #generics (#fargs) -> ::core::result::Result<#embassy_executor::SpawnToken, #embassy_executor::SpawnError> #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); } ================================================ FILE: embassy-executor-macros/src/util.rs ================================================ use std::fmt::Display; use proc_macro2::{TokenStream, TokenTree}; use quote::{ToTokens, TokenStreamExt}; use syn::parse::{Parse, ParseStream}; use syn::{AttrStyle, Attribute, Signature, Token, Visibility, braced, bracketed, token}; 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()); }); } } ================================================ FILE: embassy-executor-timer-queue/CHANGELOG.md ================================================ # Changelog for embassy-executor-timer-queue 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.1.0 - 2025-08-25 - Initial implementation ================================================ FILE: embassy-executor-timer-queue/Cargo.toml ================================================ [package] name = "embassy-executor-timer-queue" version = "0.1.0" edition = "2024" description = "Timer queue item and interface between embassy-executor and timer queues" repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-executor-timer-queue" readme = "README.md" license = "MIT OR Apache-2.0" categories = [ "embedded", "no-std", "concurrency", "asynchronous", ] [dependencies] [features] #! ### Timer Queue Item Size #! Sets the size of the timer items. ## 4 words timer-item-size-4-words = [] ## 6 words timer-item-size-6-words = [] ## 8 words timer-item-size-8-words = [] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-timer-queue-v$VERSION/embassy-executor-timer-queue/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor-timer-queue/src/" target = "x86_64-unknown-linux-gnu" [package.metadata.embassy] build = [ {target = "thumbv7em-none-eabi", features = []}, {target = "thumbv6m-none-eabi", features = []}, ] ================================================ FILE: embassy-executor-timer-queue/README.md ================================================ # embassy-executor-time-queue This crate defines the timer queue item that embassy-executor provides, and a way to access it, for executor-integrated timer queues. The crate decouples the release cycle of embassy-executor from that of the queue implementations'. As a HAL implementer, you only need to depend on this crate if you want to implement executor-integrated timer queues yourself, without using [`embassy-time-queue-utils`](https://crates.io/crates/embassy-time-queue-utils). As a HAL user, you should not need to depend on this crate. ================================================ FILE: embassy-executor-timer-queue/src/lib.rs ================================================ //! Timer queue item for embassy-executor integrated timer queues //! //! `embassy-executor` provides the memory needed to implement integrated timer queues. This crate //! exists to separate that memory from `embassy-executor` itself, to decouple the timer queue's //! release cycle from `embassy-executor`. //! //! This crate contains two things: //! - [`TimerQueueItem`]: The item type that can be requested from the executor. The size of this //! type can be configured using the `timer-item-size-N-words` Cargo features. //! - The expectation that `extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &mut TimerQueueItem` //! is implemented (by `embassy-executor`, most likely). This function must return a mutable //! reference to the `TimerQueueItem` associated with the given waker. //! //! As a queue implementor, you will need to choose one of the `timer-item-size-N-words` features to //! select a queue item size. You can then define your own item type, which must be //! `#[repr(align(8))]` (or less) and must fit into the size you selected. //! //! You can access the `TimerQueueItem` from a `Waker` using the [`from_embassy_waker`](TimerQueueItem::from_embassy_waker) //! method. You can then use the [`as_ref`](TimerQueueItem::as_ref) and [`as_mut`](TimerQueueItem::as_mut) //! methods to reinterpret the data stored in the item as your custom item type. #![no_std] use core::task::Waker; const ITEM_WORDS: usize = if cfg!(feature = "timer-item-size-8-words") { 8 } else if cfg!(feature = "timer-item-size-6-words") { 6 } else if cfg!(feature = "timer-item-size-4-words") { 4 } else { 0 }; /// The timer queue item provided by the executor. /// /// This type is opaque, it only provides the raw storage for a queue item. The queue implementation /// is responsible for reinterpreting the contents of the item using [`TimerQueueItem::as_ref`] and /// [`TimerQueueItem::as_mut`]. #[repr(align(8))] pub struct TimerQueueItem { data: [usize; ITEM_WORDS], } impl TimerQueueItem { /// Creates a new, zero-initialized `TimerQueueItem`. pub const fn new() -> Self { Self { data: [0; ITEM_WORDS] } } /// Retrieves the `TimerQueueItem` reference that belongs to the task of the waker. /// /// Panics if called with a non-embassy waker. /// /// # Safety /// /// The caller must ensure they are not violating Rust's aliasing rules - it is not allowed /// to use this method to create multiple mutable references to the same `TimerQueueItem` at /// the same time. /// /// This function must only be called in the context of a timer queue implementation. pub unsafe fn from_embassy_waker(waker: &Waker) -> &'static mut Self { unsafe extern "Rust" { // Waker -> TimerQueueItem, validates that Waker is an embassy Waker. fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static mut TimerQueueItem; } unsafe { __embassy_time_queue_item_from_waker(waker) } } /// Access the data as a reference to a type `T`. /// /// Safety: /// /// - The type must be valid when zero-initialized. /// - The timer queue should only be interpreted as a single type `T` during its lifetime. pub unsafe fn as_ref(&self) -> &T { const { validate::() } unsafe { &*(self.data.as_ptr() as *const T) } } /// Access the data as a reference to a type `T`. /// /// Safety: /// /// - The type must be valid when zero-initialized. /// - The timer queue should only be interpreted as a single type `T` during its lifetime. pub unsafe fn as_mut(&self) -> &mut T { const { validate::() } unsafe { &mut *(self.data.as_ptr() as *mut T) } } } const fn validate() { const { assert!( core::mem::size_of::() >= core::mem::size_of::(), "embassy-executor-timer-queue item size is smaller than the requested type. Select a larger timer-item-size-N-words feature." ); assert!( core::mem::align_of::() >= core::mem::align_of::(), "the alignment of the requested type is greater than 8" ); } } ================================================ FILE: embassy-futures/CHANGELOG.md ================================================ # Changelog for embassy-futures 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 - ReleaseDate ## 0.1.2 - 2025-08-26 - Preserve location information for `defmt` in `fmt` calls ([#3085](https://github.com/embassy-rs/embassy/pull/3085)) - Fixed soundness issue in `select_slice` ([#3328](https://github.com/embassy-rs/embassy/pull/3328)) - Added `select5` and `select6` ([#3430](https://github.com/embassy-rs/embassy/pull/3430)) - Added `is_x` methods for all `EitherN` enum variants (#[3650](https://github.com/embassy-rs/embassy/pull/3650)) ================================================ FILE: embassy-futures/Cargo.toml ================================================ [package] name = "embassy-futures" version = "0.1.2" edition = "2024" 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 = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } [features] defmt = ["dep:defmt"] log = ["dep:log"] ================================================ FILE: embassy-futures/README.md ================================================ # 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. ================================================ FILE: embassy-futures/src/block_on.rs ================================================ 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) } ================================================ FILE: embassy-futures/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-futures/src/join.rs ================================================ //! 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), } } ================================================ FILE: embassy-futures/src/lib.rs ================================================ #![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::*; ================================================ FILE: embassy-futures/src/select.rs ================================================ //! 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 } } ================================================ FILE: embassy-futures/src/yield_now.rs ================================================ 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 } } } ================================================ FILE: embassy-hal-internal/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.5.0 - 2026-03-20 - Make peripheral steal() const ## 0.4.0 - 2026-01-04 - Bump rust edition - Add function to return the number of available items ## 0.1.1 - 2025-08-15 - First release with changelog. ================================================ FILE: embassy-hal-internal/Cargo.toml ================================================ [package] name = "embassy-hal-internal" version = "0.5.0" edition = "2024" 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] defmt = ["dep:defmt"] log = ["dep:log"] # 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 = "1.0.1", 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 } ================================================ FILE: embassy-hal-internal/README.md ================================================ # 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. ================================================ FILE: embassy-hal-internal/build.rs ================================================ #[path = "./build_common.rs"] mod common; fn main() { let mut cfgs = common::CfgSet::new(); common::set_target_cfgs(&mut cfgs); } ================================================ FILE: embassy-hal-internal/build_common.rs ================================================ // 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")); } ================================================ FILE: embassy-hal-internal/src/atomic_ring_buffer.rs ================================================ //! 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) } /// Return number of items available to read. pub fn available(&self) -> usize { let end = self.end.load(Ordering::Relaxed); let len = self.len.load(Ordering::Relaxed); let start = self.start.load(Ordering::Relaxed); if end >= start { end - start } else { 2 * len - start + end } } /// 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 at least half full. pub fn is_half_full(&self) -> bool { self.available() >= self.len.load(Ordering::Relaxed) / 2 } /// 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_half_full(), false); 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_half_full(), true); 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_half_full(), true); 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_half_full(), true); 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_half_full(), true); 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_half_full(), false); 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 }); assert_eq!(rb.is_empty(), false); assert_eq!(rb.is_half_full(), false); assert_eq!(rb.is_full(), false); rb.writer().push(|buf| { assert_eq!(3, buf.len()); buf[0] = 11; 1 }); assert_eq!(rb.is_empty(), false); assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), false); rb.writer().push(|buf| { assert_eq!(2, buf.len()); buf[0] = 12; 1 }); assert_eq!(rb.is_empty(), false); assert_eq!(rb.is_half_full(), true); 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_half_full(), true); 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_half_full(), 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 }); } } } } ================================================ FILE: embassy-hal-internal/src/drop.rs ================================================ //! 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") } } ================================================ FILE: embassy-hal-internal/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-hal-internal/src/interrupt.rs ================================================ //! Interrupt handling for cortex-m devices. use core::mem; use core::sync::atomic::{Ordering, compiler_fence}; 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>: Copy {} } } }; } /// 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, } ================================================ FILE: embassy-hal-internal/src/lib.rs ================================================ #![no_std] #![allow(clippy::new_without_default)] #![allow(unsafe_op_in_unsafe_fn)] #![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::{Peri, PeripheralType}; #[cfg(feature = "cortex-m")] pub mod interrupt; ================================================ FILE: embassy-hal-internal/src/macros.rs ================================================ /// 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")] #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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 const unsafe fn steal() -> $crate::Peri<'static, Self> { $crate::Peri::new_unchecked(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: $crate::Peri<'static, 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 { #[unsafe(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, )* ); }; } /// Implement the peripheral trait. #[macro_export] macro_rules! impl_peripheral { ($type:ident<$($T:ident $(: $bound:tt $(+ $others:tt )*)?),*>) => { impl<$($T: $($bound $(+$others)*)?),*> Copy for $type <$($T),*> {} impl<$($T: $($bound $(+$others)*)?),*> Clone for $type <$($T),*> { fn clone(&self) -> Self { *self } } impl<$($T: $($bound $(+$others)*)?),*> PeripheralType for $type <$($T),*> {} }; ($type:ident) => { impl Copy for $type {} impl Clone for $type { fn clone(&self) -> Self { *self } } impl $crate::PeripheralType for $type {} }; } ================================================ FILE: embassy-hal-internal/src/peripheral.rs ================================================ use core::marker::PhantomData; use core::ops::Deref; /// 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. /// Peripheral 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 Peri, the driver is generic /// over a lifetime only. `SPI4` becomes `Peri<'static, SPI4>`, and `&mut SPI4` becomes /// `Peri<'a, SPI4>`. Lifetimes don't cause monomorphization. pub struct Peri<'a, T: PeripheralType> { inner: T, _lifetime: PhantomData<&'a mut T>, } impl<'a, T: PeripheralType> Peri<'a, T> { /// Create a new owned a peripheral. /// /// For use by HALs only. /// /// If you're an end user you shouldn't use this, you should use `steal()` /// on the actual peripheral types instead. #[inline] #[doc(hidden)] pub const unsafe fn new_unchecked(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 /// `Peri` that borrows `self`, which allows the borrow checker /// to enforce this at compile time. pub const unsafe fn clone_unchecked(&self) -> Peri<'a, T> { Peri::new_unchecked(self.inner) } /// Reborrow into a "child" Peri. /// /// `self` will stay borrowed until the child Peripheral is dropped. pub const fn reborrow(&mut self) -> Peri<'_, T> { // safety: we're returning the clone inside a new Peripheral that borrows // self, so user code can't use both at the same time. unsafe { self.clone_unchecked() } } /// Map the inner peripheral using `Into`. /// /// This converts from `Peri<'a, T>` to `Peri<'a, U>`, using an /// `Into` impl to convert from `T` to `U`. /// /// For example, this can be useful to.into() GPIO pins: converting from Peri<'a, PB11>` to `Peri<'a, AnyPin>`. #[inline] pub fn into(self) -> Peri<'a, U> where T: Into, U: PeripheralType, { unsafe { Peri::new_unchecked(self.inner.into()) } } } impl<'a, T: PeripheralType> Deref for Peri<'a, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.inner } } impl<'a, T: PeripheralType + core::fmt::Debug> core::fmt::Debug for Peri<'a, T> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.inner.fmt(f) } } impl<'a, T: PeripheralType + core::fmt::Display> core::fmt::Display for Peri<'a, T> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.inner.fmt(f) } } #[cfg(feature = "defmt")] impl<'a, T: PeripheralType + defmt::Format> defmt::Format for Peri<'a, T> { fn format(&self, fmt: defmt::Formatter) { self.inner.format(fmt); } } /// Marker trait for peripheral types. pub trait PeripheralType: Copy + Sized {} ================================================ FILE: embassy-hal-internal/src/ratio.rs ================================================ //! 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); } } ================================================ FILE: embassy-imxrt/CHANGELOG.md ================================================ # 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 - ReleaseDate - First release with changelog. ================================================ FILE: embassy-imxrt/Cargo.toml ================================================ [package] name = "embassy-imxrt" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for the IMXRT microcontroller" keywords = ["embedded", "async", "imxrt", "rt600", "embedded-hal"] categories = ["embedded", "hardware-support", "no-std", "asynchronous"] repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-imxrt" publish = false [package.metadata.embassy] build = [ {target = "thumbv8m.main-none-eabihf", features = ["defmt", "mimxrt633s", "time", "time-driver-rtc", "unstable-pac"]}, {target = "thumbv8m.main-none-eabihf", features = ["defmt", "mimxrt685s", "time", "time-driver-rtc", "unstable-pac"]}, ] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/" features = ["defmt", "unstable-pac", "time", "time-driver-os-timer"] flavors = [ { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" } ] [package.metadata.docs.rs] features = ["mimxrt685s", "defmt", "unstable-pac", "time", "time-driver"] rustdoc-args = ["--cfg", "docsrs"] [features] default = ["rt"] ## Cortex-M runtime (enabled by default) rt = [ "mimxrt685s-pac?/rt", "mimxrt633s-pac?/rt", ] ## Enable defmt defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxrt685s-pac?/defmt", "mimxrt633s-pac?/defmt"] ## Enable features requiring `embassy-time` time = ["dep:embassy-time", "embassy-embedded-hal/time"] ## Enable custom embassy time-driver implementation, using 32KHz RTC time-driver-rtc = ["_time-driver", "embassy-time-driver?/tick-hz-1_000"] ## Enable custom embassy time-driver implementation, using 1MHz OS Timer time-driver-os-timer = ["_time-driver", "embassy-time-driver?/tick-hz-1_000_000"] _time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] ## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) unstable-pac = [] # 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. _mimxrt685s = [] _mimxrt633s = ["_espi"] # Peripherals _espi = [] #! ### Chip selection features ## MIMXRT685S mimxrt685s = ["mimxrt685s-pac", "_mimxrt685s"] ## MIMXRT633S mimxrt633s = ["mimxrt633s-pac", "_mimxrt633s"] [dependencies] embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", optional = true } embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-utils", optional = true } embassy-time = { version = "0.5.1", path = "../embassy-time", optional = true } embassy-hal-internal = { version = "0.5.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal", default-features = false } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } nb = "1.0.0" cfg-if = "1.0.0" cortex-m-rt = ">=0.7.3,<0.8" cortex-m = "0.7.6" critical-section = "1.1" embedded-io = { version = "0.7.1" } embedded-io-async = { version = "0.7.0" } fixed = "1.23.1" rand-core-06 = { package = "rand_core", version = "0.6" } rand-core-09 = { package = "rand_core", version = "0.9" } 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" } document-features = "0.2.7" paste = "1.0" # PACs mimxrt685s-pac = { version = "0.5.0", optional = true, features = ["rt", "critical-section"] } mimxrt633s-pac = { version = "0.5.0", optional = true, features = ["rt", "critical-section"] } ================================================ FILE: embassy-imxrt/README.md ================================================ # Embassy iMXRT HAL ## Introduction HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy iMXRT HAL targets the NXP iMXRT Family of MCUs. 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 of interrupts, so that applications can focus on business logic. 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-imxrt documentation](https://docs.embassy.dev/embassy-imxrt). ## Hardware support The `embassy-imxrt` HAL currently supports two main variants of the iMXRT family: * MIMXRT685S ([examples](https://github.com/OpenDevicePartnership/embassy-imxrt/tree/main/examples/rt685s-evk)) * MIMXRT633s ([examples](https://github.com/OpenDevicePartnership/embassy-imxrt/tree/main/examples/rt633)) Several peripherals are supported and tested on both supported chip variants. To check what's available, make sure to the MCU you're targetting in the top menu in the [documentation](https://docs.embassy.dev/embassy-imxrt). ## TrustZone support TrustZone support is yet to be implemented. ## Time driver If the `time-driver` 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-imxrt` 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. ================================================ FILE: embassy-imxrt/src/chips/mimxrt633s.rs ================================================ pub use mimxrt633s_pac as pac; #[allow(clippy::missing_safety_doc)] pub mod interrupts { embassy_hal_internal::interrupt_mod!( ACMP, ADC0, CASPER, CTIMER0, CTIMER1, CTIMER2, CTIMER3, CTIMER4, DMA0, DMA1, DMIC0, ESPI, FLEXCOMM0, FLEXCOMM1, FLEXCOMM14, FLEXCOMM15, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXSPI, GPIO_INTA, GPIO_INTB, HASHCRYPT, HWVAD0, HYPERVISOR, I3C0, MRT0, MU_A, OS_EVENT, PIN_INT0, PIN_INT1, PIN_INT2, PIN_INT3, PIN_INT4, PIN_INT5, PIN_INT6, PIN_INT7, PMC_PMIC, POWERQUAD, PUF, RNG, RTC, SCT0, SECUREVIOLATION, SGPIO_INTA, SGPIO_INTB, USB, USBPHY_DCD, USB_WAKEUP, USDHC0, USDHC1, UTICK0, WDT0, WDT1, ); } embassy_hal_internal::peripherals!( ACMP, ADC0, CASPER, CRC, CTIMER0_COUNT_CHANNEL0, CTIMER0_COUNT_CHANNEL1, CTIMER0_COUNT_CHANNEL2, CTIMER0_COUNT_CHANNEL3, CTIMER0_CAPTURE_CHANNEL0, CTIMER0_CAPTURE_CHANNEL1, CTIMER0_CAPTURE_CHANNEL2, CTIMER0_CAPTURE_CHANNEL3, CTIMER1_COUNT_CHANNEL0, CTIMER1_COUNT_CHANNEL1, CTIMER1_COUNT_CHANNEL2, CTIMER1_COUNT_CHANNEL3, CTIMER1_CAPTURE_CHANNEL0, CTIMER1_CAPTURE_CHANNEL1, CTIMER1_CAPTURE_CHANNEL2, CTIMER1_CAPTURE_CHANNEL3, CTIMER2_COUNT_CHANNEL0, CTIMER2_COUNT_CHANNEL1, CTIMER2_COUNT_CHANNEL2, CTIMER2_COUNT_CHANNEL3, CTIMER2_CAPTURE_CHANNEL0, CTIMER2_CAPTURE_CHANNEL1, CTIMER2_CAPTURE_CHANNEL2, CTIMER2_CAPTURE_CHANNEL3, CTIMER3_COUNT_CHANNEL0, CTIMER3_COUNT_CHANNEL1, CTIMER3_COUNT_CHANNEL2, CTIMER3_COUNT_CHANNEL3, CTIMER3_CAPTURE_CHANNEL0, CTIMER3_CAPTURE_CHANNEL1, CTIMER3_CAPTURE_CHANNEL2, CTIMER3_CAPTURE_CHANNEL3, CTIMER4_COUNT_CHANNEL0, CTIMER4_COUNT_CHANNEL1, CTIMER4_COUNT_CHANNEL2, CTIMER4_COUNT_CHANNEL3, CTIMER4_CAPTURE_CHANNEL0, CTIMER4_CAPTURE_CHANNEL1, CTIMER4_CAPTURE_CHANNEL2, CTIMER4_CAPTURE_CHANNEL3, DMA0, DMA0_CH0, DMA0_CH1, DMA0_CH2, DMA0_CH3, DMA0_CH4, DMA0_CH5, DMA0_CH6, DMA0_CH7, DMA0_CH8, DMA0_CH9, DMA0_CH10, DMA0_CH11, DMA0_CH12, DMA0_CH13, DMA0_CH14, DMA0_CH15, DMA0_CH16, DMA0_CH17, DMA0_CH18, DMA0_CH19, DMA0_CH20, DMA0_CH21, DMA0_CH22, DMA0_CH23, DMA0_CH24, DMA0_CH25, DMA0_CH26, DMA0_CH27, DMA0_CH28, DMA0_CH29, DMA0_CH30, DMA0_CH31, DMA0_CH32, DMA1, DMA1_CH0, DMA1_CH1, DMA1_CH2, DMA1_CH3, DMA1_CH4, DMA1_CH5, DMA1_CH6, DMA1_CH7, DMA1_CH8, DMA1_CH9, DMA1_CH10, DMA1_CH11, DMA1_CH12, DMA1_CH13, DMA1_CH14, DMA1_CH15, DMA1_CH16, DMA1_CH17, DMA1_CH18, DMA1_CH19, DMA1_CH20, DMA1_CH21, DMA1_CH22, DMA1_CH23, DMA1_CH24, DMA1_CH25, DMA1_CH26, DMA1_CH27, DMA1_CH28, DMA1_CH29, DMA1_CH30, DMA1_CH31, DMA1_CH32, DMIC0, DSPWAKE, ESPI, FLEXCOMM0, FLEXCOMM1, FLEXCOMM14, FLEXCOMM15, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXSPI, FREQME, GPIO_INTA, GPIO_INTB, HASHCRYPT, HSGPIO0, HSGPIO1, HSGPIO2, HSGPIO3, HSGPIO4, HSGPIO5, HSGPIO6, HSGPIO7, HWVAD0, HYPERVISOR, I3C0, MRT0, MU_A, OS_EVENT, PIN_INT0, PIN_INT1, PIN_INT2, PIN_INT3, PIN_INT4, PIN_INT5, PIN_INT6, PIN_INT7, PIO0_0, PIO0_1, PIO0_10, PIO0_11, PIO0_12, PIO0_13, PIO0_14, PIO0_15, PIO0_16, PIO0_17, PIO0_18, PIO0_19, PIO0_2, PIO0_20, PIO0_21, PIO0_22, PIO0_23, PIO0_24, PIO0_25, PIO0_26, PIO0_27, PIO0_28, PIO0_29, PIO0_3, PIO0_30, PIO0_31, PIO0_4, PIO0_5, PIO0_6, PIO0_7, PIO0_8, PIO0_9, PIO1_0, PIO1_1, PIO1_10, PIO1_11, PIO1_12, PIO1_13, PIO1_14, PIO1_15, PIO1_16, PIO1_17, PIO1_18, PIO1_19, PIO1_2, PIO1_20, PIO1_21, PIO1_22, PIO1_23, PIO1_24, PIO1_25, PIO1_26, PIO1_27, PIO1_28, PIO1_29, PIO1_3, PIO1_30, PIO1_31, PIO1_4, PIO1_5, PIO1_6, PIO1_7, PIO1_8, PIO1_9, PIO2_0, PIO2_1, PIO2_10, PIO2_11, PIO2_12, PIO2_13, PIO2_14, PIO2_15, PIO2_16, PIO2_17, PIO2_18, PIO2_19, PIO2_2, PIO2_20, PIO2_21, PIO2_22, PIO2_23, PIO2_24, PIO2_25, PIO2_26, PIO2_27, PIO2_28, PIO2_29, PIO2_3, PIO2_30, PIO2_31, PIO2_4, PIO2_5, PIO2_6, PIO2_7, PIO2_8, PIO2_9, PIO3_0, PIO3_1, PIO3_10, PIO3_11, PIO3_12, PIO3_13, PIO3_14, PIO3_15, PIO3_16, PIO3_17, PIO3_18, PIO3_19, PIO3_2, PIO3_20, PIO3_21, PIO3_22, PIO3_23, PIO3_24, PIO3_25, PIO3_26, PIO3_27, PIO3_28, PIO3_29, PIO3_3, PIO3_30, PIO3_31, PIO3_4, PIO3_5, PIO3_6, PIO3_7, PIO3_8, PIO3_9, PIO4_0, PIO4_1, PIO4_10, PIO4_2, PIO4_3, PIO4_4, PIO4_5, PIO4_6, PIO4_7, PIO4_8, PIO4_9, PIO7_24, PIO7_25, PIO7_26, PIO7_27, PIO7_28, PIO7_29, PIO7_30, PIO7_31, PIOFC15_SCL, PIOFC15_SDA, PMC_PMIC, PIMCTL, POWERQUAD, PUF, RNG, RTC, SCT0, SECGPIO, SECUREVIOLATION, SEMA42, SGPIO_INTA, SGPIO_INTB, USBHSD, USBHSH, USBPHY, USB_WAKEUP, USDHC0, USDHC1, UTICK0, WDT0, WDT1, ); ================================================ FILE: embassy-imxrt/src/chips/mimxrt685s.rs ================================================ pub use mimxrt685s_pac as pac; #[allow(clippy::missing_safety_doc)] pub mod interrupts { embassy_hal_internal::interrupt_mod!( ACMP, ADC0, CASPER, CTIMER0, CTIMER1, CTIMER2, CTIMER3, CTIMER4, DMA0, DMA1, DMIC0, DSPWAKE, FLEXCOMM0, FLEXCOMM1, FLEXCOMM14, FLEXCOMM15, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXSPI, GPIO_INTA, GPIO_INTB, HASHCRYPT, HWVAD0, HYPERVISOR, I3C0, MRT0, MU_A, OS_EVENT, PIN_INT0, PIN_INT1, PIN_INT2, PIN_INT3, PIN_INT4, PIN_INT5, PIN_INT6, PIN_INT7, PMC_PMIC, POWERQUAD, PUF, RNG, RTC, SCT0, SECUREVIOLATION, SGPIO_INTA, SGPIO_INTB, USB, USBPHY_DCD, USB_WAKEUP, USDHC0, USDHC1, UTICK0, WDT0, WDT1, ); } embassy_hal_internal::peripherals!( ACMP, ADC0, CASPER, CRC, CTIMER0_COUNT_CHANNEL0, CTIMER0_COUNT_CHANNEL1, CTIMER0_COUNT_CHANNEL2, CTIMER0_COUNT_CHANNEL3, CTIMER0_CAPTURE_CHANNEL0, CTIMER0_CAPTURE_CHANNEL1, CTIMER0_CAPTURE_CHANNEL2, CTIMER0_CAPTURE_CHANNEL3, CTIMER1_COUNT_CHANNEL0, CTIMER1_COUNT_CHANNEL1, CTIMER1_COUNT_CHANNEL2, CTIMER1_COUNT_CHANNEL3, CTIMER1_CAPTURE_CHANNEL0, CTIMER1_CAPTURE_CHANNEL1, CTIMER1_CAPTURE_CHANNEL2, CTIMER1_CAPTURE_CHANNEL3, CTIMER2_COUNT_CHANNEL0, CTIMER2_COUNT_CHANNEL1, CTIMER2_COUNT_CHANNEL2, CTIMER2_COUNT_CHANNEL3, CTIMER2_CAPTURE_CHANNEL0, CTIMER2_CAPTURE_CHANNEL1, CTIMER2_CAPTURE_CHANNEL2, CTIMER2_CAPTURE_CHANNEL3, CTIMER3_COUNT_CHANNEL0, CTIMER3_COUNT_CHANNEL1, CTIMER3_COUNT_CHANNEL2, CTIMER3_COUNT_CHANNEL3, CTIMER3_CAPTURE_CHANNEL0, CTIMER3_CAPTURE_CHANNEL1, CTIMER3_CAPTURE_CHANNEL2, CTIMER3_CAPTURE_CHANNEL3, CTIMER4_COUNT_CHANNEL0, CTIMER4_COUNT_CHANNEL1, CTIMER4_COUNT_CHANNEL2, CTIMER4_COUNT_CHANNEL3, CTIMER4_CAPTURE_CHANNEL0, CTIMER4_CAPTURE_CHANNEL1, CTIMER4_CAPTURE_CHANNEL2, CTIMER4_CAPTURE_CHANNEL3, DMA0, DMA0_CH0, DMA0_CH1, DMA0_CH2, DMA0_CH3, DMA0_CH4, DMA0_CH5, DMA0_CH6, DMA0_CH7, DMA0_CH8, DMA0_CH9, DMA0_CH10, DMA0_CH11, DMA0_CH12, DMA0_CH13, DMA0_CH14, DMA0_CH15, DMA0_CH16, DMA0_CH17, DMA0_CH18, DMA0_CH19, DMA0_CH20, DMA0_CH21, DMA0_CH22, DMA0_CH23, DMA0_CH24, DMA0_CH25, DMA0_CH26, DMA0_CH27, DMA0_CH28, DMA0_CH29, DMA0_CH30, DMA0_CH31, DMA0_CH32, DMA1, DMA1_CH0, DMA1_CH1, DMA1_CH2, DMA1_CH3, DMA1_CH4, DMA1_CH5, DMA1_CH6, DMA1_CH7, DMA1_CH8, DMA1_CH9, DMA1_CH10, DMA1_CH11, DMA1_CH12, DMA1_CH13, DMA1_CH14, DMA1_CH15, DMA1_CH16, DMA1_CH17, DMA1_CH18, DMA1_CH19, DMA1_CH20, DMA1_CH21, DMA1_CH22, DMA1_CH23, DMA1_CH24, DMA1_CH25, DMA1_CH26, DMA1_CH27, DMA1_CH28, DMA1_CH29, DMA1_CH30, DMA1_CH31, DMA1_CH32, DMIC0, DSPWAKE, FLEXCOMM0, FLEXCOMM1, FLEXCOMM14, FLEXCOMM15, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXSPI, FREQME, GPIO_INTA, GPIO_INTB, HASHCRYPT, HSGPIO0, HSGPIO1, HSGPIO2, HSGPIO3, HSGPIO4, HSGPIO5, HSGPIO6, HSGPIO7, HWVAD0, HYPERVISOR, I3C0, MRT0, MU_A, OS_EVENT, PIN_INT0, PIN_INT1, PIN_INT2, PIN_INT3, PIN_INT4, PIN_INT5, PIN_INT6, PIN_INT7, PIO0_0, PIO0_1, PIO0_10, PIO0_11, PIO0_12, PIO0_13, PIO0_14, PIO0_15, PIO0_16, PIO0_17, PIO0_18, PIO0_19, PIO0_2, PIO0_20, PIO0_21, PIO0_22, PIO0_23, PIO0_24, PIO0_25, PIO0_26, PIO0_27, PIO0_28, PIO0_29, PIO0_3, PIO0_30, PIO0_31, PIO0_4, PIO0_5, PIO0_6, PIO0_7, PIO0_8, PIO0_9, PIO1_0, PIO1_1, PIO1_10, PIO1_11, PIO1_12, PIO1_13, PIO1_14, PIO1_15, PIO1_16, PIO1_17, PIO1_18, PIO1_19, PIO1_2, PIO1_20, PIO1_21, PIO1_22, PIO1_23, PIO1_24, PIO1_25, PIO1_26, PIO1_27, PIO1_28, PIO1_29, PIO1_3, PIO1_30, PIO1_31, PIO1_4, PIO1_5, PIO1_6, PIO1_7, PIO1_8, PIO1_9, PIO2_0, PIO2_1, PIO2_10, PIO2_11, PIO2_12, PIO2_13, PIO2_14, PIO2_15, PIO2_16, PIO2_17, PIO2_18, PIO2_19, PIO2_2, PIO2_20, PIO2_21, PIO2_22, PIO2_23, PIO2_24, PIO2_25, PIO2_26, PIO2_27, PIO2_28, PIO2_29, PIO2_3, PIO2_30, PIO2_31, PIO2_4, PIO2_5, PIO2_6, PIO2_7, PIO2_8, PIO2_9, PIO3_0, PIO3_1, PIO3_10, PIO3_11, PIO3_12, PIO3_13, PIO3_14, PIO3_15, PIO3_16, PIO3_17, PIO3_18, PIO3_19, PIO3_2, PIO3_20, PIO3_21, PIO3_22, PIO3_23, PIO3_24, PIO3_25, PIO3_26, PIO3_27, PIO3_28, PIO3_29, PIO3_3, PIO3_30, PIO3_31, PIO3_4, PIO3_5, PIO3_6, PIO3_7, PIO3_8, PIO3_9, PIO4_0, PIO4_1, PIO4_10, PIO4_2, PIO4_3, PIO4_4, PIO4_5, PIO4_6, PIO4_7, PIO4_8, PIO4_9, PIO7_24, PIO7_25, PIO7_26, PIO7_27, PIO7_28, PIO7_29, PIO7_30, PIO7_31, PIOFC15_SCL, PIOFC15_SDA, PMC_PMIC, PIMCTL, POWERQUAD, PUF, RNG, RTC, SCT0, SECGPIO, SECUREVIOLATION, SEMA42, SGPIO_INTA, SGPIO_INTB, USBHSD, USBHSH, USBPHY, USB_WAKEUP, USDHC0, USDHC1, UTICK0, WDT0, WDT1, ); ================================================ FILE: embassy-imxrt/src/clocks.rs ================================================ //! Clock configuration for the `RT6xx` use core::sync::atomic::{AtomicU8, AtomicU32, Ordering}; use paste::paste; use crate::pac; /// Clock configuration; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Clocks { /// Low power oscillator Lposc, /// System Frequency Resonance Oscillator (SFRO) Sfro, /// Real Time Clock Rtc, /// Feed-forward Ring Oscillator Ffro, // This includes that div2 and div4 variations /// External Clock Input ClkIn, /// AHB Clock Hclk, /// Main Clock MainClk, /// Main PLL Clock MainPllClk, // also has aux0,aux1,dsp, and audio pll's downstream /// System Clock SysClk, /// System Oscillator SysOscClk, /// ADC Clock Adc, } /// Clock configuration. pub struct ClockConfig { /// low-power oscillator config pub lposc: LposcConfig, /// 16Mhz internal oscillator config pub sfro: SfroConfig, /// Real Time Clock config pub rtc: RtcClkConfig, /// 48/60 Mhz internal oscillator config pub ffro: FfroConfig, // pub pll: Option, //potentially covered in main pll clk /// External Clock-In config pub clk_in: ClkInConfig, /// AHB bus clock config pub hclk: HclkConfig, /// Main Clock config pub main_clk: MainClkConfig, /// Main Pll clock config pub main_pll_clk: MainPllClkConfig, /// Software concept to be used with systick, doesn't map to a register pub sys_clk: SysClkConfig, /// System Oscillator Config pub sys_osc: SysOscConfig, // todo: move ADC here } impl ClockConfig { /// Clock configuration derived from external crystal. #[must_use] pub fn crystal() -> Self { const CORE_CPU_FREQ: u32 = 500_000_000; const PLL_CLK_FREQ: u32 = 528_000_000; const SYS_CLK_FREQ: u32 = CORE_CPU_FREQ / 2; Self { lposc: LposcConfig { state: State::Enabled, freq: AtomicU32::new(Into::into(LposcFreq::Lp1m)), }, sfro: SfroConfig { state: State::Enabled }, rtc: RtcClkConfig { state: State::Enabled, wake_alarm_state: State::Disabled, sub_second_state: State::Disabled, freq: AtomicU32::new(Into::into(RtcFreq::Default1Hz)), rtc_int: RtcInterrupts::None, }, ffro: FfroConfig { state: State::Enabled, freq: AtomicU32::new(Into::into(FfroFreq::Ffro48m)), }, //pll: Some(PllConfig {}),//includes aux0 and aux1 pll clk_in: ClkInConfig { state: State::Disabled, // This is an externally sourced clock // Don't give it an initial frequency freq: Some(AtomicU32::new(0)), }, hclk: HclkConfig { state: State::Disabled }, main_clk: MainClkConfig { state: State::Enabled, //FFRO divided by 4 is reset values of Main Clk Sel A, Sel B src: MainClkSrc::FFRO, div_int: AtomicU32::new(4), freq: AtomicU32::new(CORE_CPU_FREQ), }, main_pll_clk: MainPllClkConfig { state: State::Enabled, src: MainPllClkSrc::SFRO, freq: AtomicU32::new(PLL_CLK_FREQ), mult: AtomicU8::new(16), pfd0: 19, // pfd1: 0, // future field pfd2: 19, // 0x13 pfd3: 0, // future field aux0_div: 0, aux1_div: 0, }, sys_clk: SysClkConfig { sysclkfreq: AtomicU32::new(SYS_CLK_FREQ), }, sys_osc: SysOscConfig { state: State::Enabled }, //adc: Some(AdcConfig {}), // TODO: add config } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Clock state enum pub enum State { /// Clock is enabled Enabled, /// Clock is disabled Disabled, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Low Power Oscillator valid frequencies pub enum LposcFreq { /// 1 `MHz` oscillator Lp1m, /// 32kHz oscillator Lp32k, } impl From for u32 { fn from(value: LposcFreq) -> Self { match value { LposcFreq::Lp1m => 1_000_000, LposcFreq::Lp32k => 32_768, } } } impl TryFrom for LposcFreq { type Error = ClockError; fn try_from(value: u32) -> Result { match value { 1_000_000 => Ok(LposcFreq::Lp1m), 32_768 => Ok(LposcFreq::Lp32k), _ => Err(ClockError::InvalidFrequency), } } } /// Low power oscillator config pub struct LposcConfig { state: State, // low power osc freq: AtomicU32, } const SFRO_FREQ: u32 = 16_000_000; /// SFRO config pub struct SfroConfig { state: State, } /// Valid RTC frequencies pub enum RtcFreq { /// "Alarm" aka 1Hz clock Default1Hz, /// "Wake" aka 1kHz clock HighResolution1khz, /// 32kHz clock SubSecond32kHz, } impl From for u32 { fn from(value: RtcFreq) -> Self { match value { RtcFreq::Default1Hz => 1, RtcFreq::HighResolution1khz => 1_000, RtcFreq::SubSecond32kHz => 32_768, } } } impl TryFrom for RtcFreq { type Error = ClockError; fn try_from(value: u32) -> Result { match value { 1 => Ok(RtcFreq::Default1Hz), 1_000 => Ok(RtcFreq::HighResolution1khz), 32_768 => Ok(RtcFreq::SubSecond32kHz), _ => Err(ClockError::InvalidFrequency), } } } /// RTC Interrupt options pub enum RtcInterrupts { /// No interrupts are set None, /// 1Hz RTC clock aka Alarm interrupt set Alarm, /// 1kHz RTC clock aka Wake interrupt set Wake, } impl From for u8 { fn from(value: RtcInterrupts) -> Self { match value { RtcInterrupts::None => 0b00, RtcInterrupts::Alarm => 0b01, RtcInterrupts::Wake => 0b10, } } } /// RTC clock config. pub struct RtcClkConfig { /// 1 Hz Clock state pub state: State, /// 1kHz Clock state pub wake_alarm_state: State, /// 32kHz Clock state pub sub_second_state: State, /// RTC clock source. pub freq: AtomicU32, /// RTC Interrupt pub rtc_int: RtcInterrupts, } /// Valid FFRO Frequencies pub enum FfroFreq { /// 48 Mhz Internal Oscillator Ffro48m, /// 60 `MHz` Internal Oscillator Ffro60m, } /// FFRO Clock Config pub struct FfroConfig { /// FFRO Clock state state: State, /// FFRO Frequency freq: AtomicU32, } impl From for u32 { fn from(value: FfroFreq) -> Self { match value { FfroFreq::Ffro48m => 48_000_000, FfroFreq::Ffro60m => 60_000_000, } } } impl TryFrom for FfroFreq { type Error = ClockError; fn try_from(value: u32) -> Result { match value { 48_000_000 => Ok(FfroFreq::Ffro48m), 60_000_000 => Ok(FfroFreq::Ffro60m), _ => Err(ClockError::InvalidFrequency), } } } /// PLL clock source #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum MainPllClkSrc { /// SFRO SFRO, /// External Clock ClkIn, /// FFRO FFRO, } /// Transform from Source Clock enum to Clocks impl From for Clocks { fn from(value: MainPllClkSrc) -> Self { match value { MainPllClkSrc::SFRO => Clocks::Sfro, MainPllClkSrc::ClkIn => Clocks::ClkIn, MainPllClkSrc::FFRO => Clocks::Ffro, } } } impl TryFrom for MainPllClkSrc { type Error = ClockError; fn try_from(value: Clocks) -> Result { match value { Clocks::Sfro => Ok(MainPllClkSrc::SFRO), Clocks::Ffro => Ok(MainPllClkSrc::FFRO), Clocks::ClkIn => Ok(MainPllClkSrc::ClkIn), _ => Err(ClockError::ClockNotSupported), } } } /// PLL configuration. pub struct MainPllClkConfig { /// Clock active state pub state: State, /// Main clock source. pub src: MainPllClkSrc, /// Main clock frequency pub freq: AtomicU32, //TODO: numerator and denominator not used but present in register /// Multiplication factor. pub mult: AtomicU8, // the following are actually 6-bits not 8 /// Fractional divider 0, main pll clock pub pfd0: u8, /// Fractional divider 1 pub pfd1: u8, /// Fractional divider 2 pub pfd2: u8, /// Fractional divider 3 pub pfd3: u8, // Aux dividers /// aux divider 0 pub aux0_div: u8, /// aux divider 1 pub aux1_div: u8, } /// External input clock config pub struct ClkInConfig { /// External clock input state state: State, /// External clock input rate freq: Option, } /// AHB clock config pub struct HclkConfig { /// divider to turn main clk into hclk for AHB bus pub state: State, } /// Main clock source. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum MainClkSrc { /// FFRO divided by 4 FFROdiv4, // probably don't need since it'll be covered by div_int /// External Clock ClkIn, /// Low Power Oscillator Lposc, /// FFRO FFRO, /// SFRO SFRO, /// Main PLL Clock PllMain, /// RTC 32kHz oscillator. RTC32k, } impl From for Clocks { fn from(value: MainClkSrc) -> Self { match value { MainClkSrc::ClkIn => Clocks::ClkIn, MainClkSrc::Lposc => Clocks::Lposc, MainClkSrc::FFRO => Clocks::Ffro, MainClkSrc::SFRO => Clocks::Sfro, MainClkSrc::PllMain => Clocks::MainPllClk, MainClkSrc::RTC32k => Clocks::Rtc, MainClkSrc::FFROdiv4 => Clocks::Ffro, } } } impl TryFrom for MainClkSrc { type Error = ClockError; fn try_from(value: Clocks) -> Result { match value { Clocks::ClkIn => Ok(MainClkSrc::ClkIn), Clocks::Lposc => Ok(MainClkSrc::Lposc), Clocks::Sfro => Ok(MainClkSrc::SFRO), Clocks::MainPllClk => Ok(MainClkSrc::PllMain), Clocks::Rtc => Ok(MainClkSrc::RTC32k), Clocks::Ffro => Ok(MainClkSrc::FFRO), _ => Err(ClockError::ClockNotSupported), } } } /// Main clock config. pub struct MainClkConfig { /// Main clock state pub state: State, /// Main clock source. pub src: MainClkSrc, /// Main clock divider. pub div_int: AtomicU32, /// Clock Frequency pub freq: AtomicU32, } /// System Core Clock config, SW concept for systick pub struct SysClkConfig { /// keeps track of the system core clock frequency /// future use with systick pub sysclkfreq: AtomicU32, } /// System Oscillator Config pub struct SysOscConfig { /// Clock State pub state: State, } const SYS_OSC_DEFAULT_FREQ: u32 = 24_000_000; /// Clock Errors #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClockError { /// Error due to attempting to change a clock with the wrong config block ClockMismatch, /// Error due to attempting to modify a clock that's not yet been enabled ClockNotEnabled, /// Error due to attempting to set a clock source that's not a supported option ClockNotSupported, /// Error due to attempting to set a clock to an invalid frequency InvalidFrequency, /// Error due to attempting to modify a clock output with an invalid divider InvalidDiv, /// Error due to attempting to modify a clock output with an invalid multiplier InvalidMult, } /// Trait to configure one of the clocks pub trait ConfigurableClock { /// Reset the clock, will enable it fn disable(&self) -> Result<(), ClockError>; /// Enable the clock fn enable_and_reset(&self) -> Result<(), ClockError>; /// Return the clock rate (Hz) fn get_clock_rate(&self) -> Result; /// Set the desired clock rate (Hz) fn set_clock_rate(&mut self, div: u8, mult: u8, freq: u32) -> Result<(), ClockError>; /// Returns whether this clock is enabled fn is_enabled(&self) -> bool; } impl LposcConfig { /// Initializes low-power oscillator. fn init_lposc() { // Enable low power oscillator // SAFETY: unsafe needed to take pointer to Sysctl0, only happens once during init let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; sysctl0.pdruncfg0_clr().write(|w| w.lposc_pd().clr_pdruncfg0()); // Wait for low-power oscillator to be ready (typically 64 us) // Busy loop seems better here than trying to shoe-in an async delay // SAFETY: unsafe needed to take pointer to Clkctl0, needed to validate HW is ready let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; while clkctl0.lposcctl0().read().clkrdy().bit_is_clear() {} } } impl ConfigurableClock for LposcConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { LposcConfig::init_lposc(); Ok(()) } fn disable(&self) -> Result<(), ClockError> { // SAFETY: unsafe needed to take pointer to Sysctl0, needed to power down the LPOSC HW let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; sysctl0.pdruncfg0_set().write(|w| w.lposc_pd().set_pdruncfg0()); // Wait until LPOSC disabled while !sysctl0.pdruncfg0().read().lposc_pd().is_power_down() {} Ok(()) } fn get_clock_rate(&self) -> Result { Ok(self.freq.load(Ordering::Relaxed)) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { if let Ok(r) = >::try_into(freq) { match r { LposcFreq::Lp1m => { self.freq .store(LposcFreq::Lp1m as u32, core::sync::atomic::Ordering::Relaxed); Ok(()) } LposcFreq::Lp32k => { self.freq .store(LposcFreq::Lp1m as u32, core::sync::atomic::Ordering::Relaxed); Ok(()) } } } else { Err(ClockError::InvalidFrequency) } } fn is_enabled(&self) -> bool { self.state == State::Enabled } } impl FfroConfig { /// Necessary register writes to initialize the FFRO clock pub fn init_ffro_clk() { // SAFETY: unsafe needed to take pointer to Sysctl0, only to power up FFRO let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; /* Power on FFRO (48/60MHz) */ sysctl0.pdruncfg0_clr().write(|w| w.ffro_pd().clr_pdruncfg0()); // SAFETY: unsafe needed to take pointer to Clkctl0, only to set proper ffro update mode let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; clkctl0.ffroctl1().write(|w| w.update().normal_mode()); // No FFRO enable/disable control in CLKCTL. // Delay enough for FFRO to be stable in case it was just powered on delay_loop_clocks(50, 12_000_000); } } impl ConfigurableClock for FfroConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { // SAFETY: should be called once FfroConfig::init_ffro_clk(); // default is 48 MHz Ok(()) } fn disable(&self) -> Result<(), ClockError> { // SAFETY: unsafe needed to take pointer to Sysctl0, only to power down FFRO let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; sysctl0.pdruncfg0_set().write(|w| w.ffro_pd().set_pdruncfg0()); delay_loop_clocks(30, 12_000_000); // Wait until FFRO disabled while !sysctl0.pdruncfg0().read().ffro_pd().is_power_down() {} Ok(()) } fn get_clock_rate(&self) -> Result { Ok(self.freq.load(Ordering::Relaxed)) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { if let Ok(r) = >::try_into(freq) { match r { FfroFreq::Ffro48m => { // SAFETY: unsafe needed to take pointer to Clkctl0, needed to set the right HW frequency let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; clkctl0.ffroctl1().write(|w| w.update().update_safe_mode()); clkctl0.ffroctl0().write(|w| w.trim_range().ffro_48mhz()); clkctl0.ffroctl1().write(|w| w.update().normal_mode()); self.freq .store(FfroFreq::Ffro48m as u32, core::sync::atomic::Ordering::Relaxed); Ok(()) } FfroFreq::Ffro60m => { // SAFETY: unsafe needed to take pointer to Clkctl0, needed to set the right HW frequency let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; clkctl0.ffroctl1().write(|w| w.update().update_safe_mode()); clkctl0.ffroctl0().write(|w| w.trim_range().ffro_60mhz()); clkctl0.ffroctl1().write(|w| w.update().normal_mode()); self.freq .store(FfroFreq::Ffro60m as u32, core::sync::atomic::Ordering::Relaxed); Ok(()) } } } else { error!("failed to convert desired clock rate, {:#}, to FFRO Freq", freq); Err(ClockError::InvalidFrequency) } } fn is_enabled(&self) -> bool { self.state == State::Enabled } } impl ConfigurableClock for SfroConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { // SAFETY: unsafe needed to take pointer to Sysctl0, only to power up SFRO let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; sysctl0.pdruncfg0_clr().write(|w| w.sfro_pd().clr_pdruncfg0()); // wait until ready while !sysctl0.pdruncfg0().read().sfro_pd().is_enabled() {} Ok(()) } fn disable(&self) -> Result<(), ClockError> { // SAFETY: unsafe needed to take pointer to Sysctl0, only to power down SFRO let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; sysctl0.pdruncfg0_set().write(|w| w.sfro_pd().set_pdruncfg0()); delay_loop_clocks(30, 12_000_000); // Wait until SFRO disabled while !sysctl0.pdruncfg0().read().sfro_pd().is_power_down() {} Ok(()) } fn get_clock_rate(&self) -> Result { if self.state == State::Enabled { Ok(SFRO_FREQ) } else { Err(ClockError::ClockNotEnabled) } } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { if self.state == State::Enabled { if freq == SFRO_FREQ { Ok(()) } else { Err(ClockError::InvalidFrequency) } } else { Err(ClockError::ClockNotEnabled) } } fn is_enabled(&self) -> bool { self.state == State::Enabled } } /// A Clock with multiple options for clock source pub trait MultiSourceClock { /// Returns which clock is being used as the clock source and its rate fn get_clock_source_and_rate(&self, clock: &Clocks) -> Result<(Clocks, u32), ClockError>; /// Sets a specific clock source and its associated rate fn set_clock_source_and_rate( &mut self, clock_src_config: &mut impl ConfigurableClock, clock_src: &Clocks, rate: u32, ) -> Result<(), ClockError>; } impl MultiSourceClock for MainPllClkConfig { fn get_clock_source_and_rate(&self, clock: &Clocks) -> Result<(Clocks, u32), ClockError> { match clock { Clocks::MainPllClk => { let converted_clock = Clocks::from(self.src); Ok((converted_clock, self.freq.load(Ordering::Relaxed))) } _ => Err(ClockError::ClockMismatch), } } fn set_clock_source_and_rate( &mut self, clock_src_config: &mut impl ConfigurableClock, clock_src: &Clocks, rate: u32, ) -> Result<(), ClockError> { if let Ok(c) = >::try_into(*clock_src) { match c { MainPllClkSrc::ClkIn => { self.src = MainPllClkSrc::ClkIn; // div mult and rate don't matter since this is an external clock self.set_clock_rate(1, 1, rate) } MainPllClkSrc::FFRO => { // FFRO Clock is divided by 2 let r = clock_src_config.get_clock_rate()?; let base_rate = r / 2; let m = MainPllClkConfig::calc_mult(rate, base_rate)?; self.src = MainPllClkSrc::FFRO; self.set_clock_rate(2, m, rate) } MainPllClkSrc::SFRO => { if !clock_src_config.is_enabled() { return Err(ClockError::ClockNotEnabled); } // check if desired frequency is a valid multiple of 16m SFRO clock let m = MainPllClkConfig::calc_mult(rate, SFRO_FREQ)?; self.src = MainPllClkSrc::SFRO; self.set_clock_rate(1, m, rate) } } } else { Err(ClockError::ClockNotSupported) } } } impl ConfigurableClock for MainPllClkConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { MainPllClkConfig::init_syspll(); MainPllClkConfig::init_syspll_pfd0(self.pfd0); MainPllClkConfig::init_syspll_pfd2(self.pfd2); Ok(()) } fn disable(&self) -> Result<(), ClockError> { if self.is_enabled() { Err(ClockError::ClockNotSupported) } else { Err(ClockError::ClockNotEnabled) } } fn get_clock_rate(&self) -> Result { if self.is_enabled() { let (_c, rate) = self.get_clock_source_and_rate(&Clocks::MainPllClk)?; Ok(rate) } else { Err(ClockError::ClockNotEnabled) } } fn set_clock_rate(&mut self, div: u8, mult: u8, freq: u32) -> Result<(), ClockError> { if self.is_enabled() { // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0 let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; // Power down pll before changes sysctl0 .pdruncfg0_set() .write(|w| w.syspllldo_pd().set_pdruncfg0().syspllana_pd().set_pdruncfg0()); let desired_freq: u64 = self.freq.load(Ordering::Relaxed).into(); match self.src { c if c == MainPllClkSrc::ClkIn || c == MainPllClkSrc::FFRO || c == MainPllClkSrc::SFRO => { let mut base_rate; match c { MainPllClkSrc::ClkIn => { clkctl0.syspll0clksel().write(|w| w.sel().sysxtal_clk()); let r = self.get_clock_rate()?; base_rate = r; } MainPllClkSrc::FFRO => { delay_loop_clocks(1000, desired_freq); match clkctl0.ffroctl0().read().trim_range().is_ffro_48mhz() { true => base_rate = Into::into(FfroFreq::Ffro48m), false => base_rate = Into::into(FfroFreq::Ffro60m), } if div == 2 { clkctl0.syspll0clksel().write(|w| w.sel().ffro_div_2()); delay_loop_clocks(150, desired_freq); base_rate /= 2; } else { return Err(ClockError::InvalidDiv); } } MainPllClkSrc::SFRO => { base_rate = SFRO_FREQ; clkctl0.syspll0clksel().write(|w| w.sel().sfro_clk()); } }; base_rate *= u32::from(mult); if base_rate != freq { // make sure to power syspll back up before returning the error // Clear System PLL reset clkctl0.syspll0ctl0().write(|w| w.reset().normal()); // Power up SYSPLL sysctl0 .pdruncfg0_clr() .write(|w| w.syspllana_pd().clr_pdruncfg0().syspllldo_pd().clr_pdruncfg0()); return Err(ClockError::InvalidFrequency); } // SAFETY: unsafe needed to write the bits for the num and demon fields clkctl0.syspll0num().write(|w| unsafe { w.num().bits(0b0) }); clkctl0.syspll0denom().write(|w| unsafe { w.denom().bits(0b1) }); delay_loop_clocks(30, desired_freq); self.mult.store(mult, Ordering::Relaxed); match mult { 16 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_16()); } 17 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_17()); } 20 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_20()); } 22 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_22()); } 27 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_27()); } 33 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_33()); } _ => return Err(ClockError::InvalidMult), } // Clear System PLL reset clkctl0.syspll0ctl0().modify(|_r, w| w.reset().normal()); // Power up SYSPLL sysctl0 .pdruncfg0_clr() .write(|w| w.syspllana_pd().clr_pdruncfg0().syspllldo_pd().clr_pdruncfg0()); // Set System PLL HOLDRINGOFF_ENA clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().enable()); delay_loop_clocks(75, desired_freq); // Clear System PLL HOLDRINGOFF_ENA clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().dsiable()); delay_loop_clocks(15, desired_freq); // gate the output and clear bits. // SAFETY: unsafe needed to write the bits for pfd0 clkctl0 .syspll0pfd() .modify(|_, w| unsafe { w.pfd0_clkgate().gated().pfd0().bits(0x0) }); // set pfd bits and un-gate the clock output // output is multiplied by syspll * 18/pfd0_bits // SAFETY: unsafe needed to write the bits for pfd0 clkctl0 .syspll0pfd() .modify(|_r, w| unsafe { w.pfd0_clkgate().not_gated().pfd0().bits(0x12) }); // wait for ready bit to be set delay_loop_clocks(50, desired_freq); while clkctl0.syspll0pfd().read().pfd0_clkrdy().bit_is_clear() {} // clear by writing a 1 clkctl0.syspll0pfd().modify(|_, w| w.pfd0_clkrdy().set_bit()); Ok(()) } _ => Err(ClockError::ClockNotSupported), } } else { Err(ClockError::ClockNotEnabled) } } fn is_enabled(&self) -> bool { self.state == State::Enabled } } impl MainPllClkConfig { /// Calculate the mult value of a desired frequency, return error if invalid pub(self) fn calc_mult(rate: u32, base_freq: u32) -> Result { const VALIDMULTS: [u8; 6] = [16, 17, 20, 22, 27, 33]; if rate > base_freq && rate % base_freq == 0 { let mult = (rate / base_freq) as u8; if VALIDMULTS.into_iter().any(|i| i == mult) { Ok(mult) } else { Err(ClockError::InvalidFrequency) } } else { Err(ClockError::InvalidFrequency) } } pub(self) fn init_syspll() { // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0 let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; // Power down SYSPLL before change fractional settings sysctl0 .pdruncfg0_set() .write(|w| w.syspllldo_pd().set_pdruncfg0().syspllana_pd().set_pdruncfg0()); clkctl0.syspll0clksel().write(|w| w.sel().ffro_div_2()); // SAFETY: unsafe needed to write the bits for both num and denom clkctl0.syspll0num().write(|w| unsafe { w.num().bits(0x0) }); clkctl0.syspll0denom().write(|w| unsafe { w.denom().bits(0x1) }); // kCLOCK_SysPllMult22 clkctl0.syspll0ctl0().modify(|_, w| w.mult().div_22()); // Clear System PLL reset clkctl0.syspll0ctl0().modify(|_, w| w.reset().normal()); // Power up SYSPLL sysctl0 .pdruncfg0_clr() .write(|w| w.syspllldo_pd().clr_pdruncfg0().syspllana_pd().clr_pdruncfg0()); delay_loop_clocks((150 & 0xFFFF) / 2, 12_000_000); // Set System PLL HOLDRINGOFF_ENA clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().enable()); delay_loop_clocks((150 & 0xFFFF) / 2, 12_000_000); // Clear System PLL HOLDRINGOFF_ENA clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().dsiable()); delay_loop_clocks((15 & 0xFFFF) / 2, 12_000_000); } /// enables default settings for pfd2 bits pub(self) fn init_syspll_pfd2(config_bits: u8) { // SAFETY: unsafe needed to take pointer to Clkctl0 and write specific bits // needed to change the output of pfd0 unsafe { let clkctl0 = crate::pac::Clkctl0::steal(); // Disable the clock output first. // SAFETY: unsafe needed to write the bits for pfd2 clkctl0 .syspll0pfd() .modify(|_, w| w.pfd2_clkgate().gated().pfd2().bits(0x0)); // Set the new value and enable output. // SAFETY: unsafe needed to write the bits for pfd2 clkctl0 .syspll0pfd() .modify(|_, w| w.pfd2_clkgate().not_gated().pfd2().bits(config_bits)); // Wait for output becomes stable. while clkctl0.syspll0pfd().read().pfd2_clkrdy().bit_is_clear() {} // Clear ready status flag. clkctl0.syspll0pfd().modify(|_, w| w.pfd2_clkrdy().clear_bit()); } } /// Enables default settings for pfd0 pub(self) fn init_syspll_pfd0(config_bits: u8) { // SAFETY: unsafe needed to take pointer to Clkctl0 and write specific bits // needed to change the output of pfd0 unsafe { let clkctl0 = crate::pac::Clkctl0::steal(); // Disable the clock output first clkctl0 .syspll0pfd() .modify(|_, w| w.pfd0_clkgate().gated().pfd0().bits(0x0)); // Set the new value and enable output clkctl0 .syspll0pfd() .modify(|_, w| w.pfd0_clkgate().not_gated().pfd0().bits(config_bits)); // Wait for output becomes stable while clkctl0.syspll0pfd().read().pfd0_clkrdy().bit_is_clear() {} // Clear ready status flag clkctl0.syspll0pfd().modify(|_, w| w.pfd0_clkrdy().clear_bit()); } } } impl MainClkConfig { fn init_main_clk() { // SAFETY:: unsafe needed to take pointers to Clkctl0 and Clkctl1 // used to set the right HW frequency let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; clkctl0.mainclkselb().write(|w| w.sel().main_pll_clk()); // Set PFC0DIV divider to value 2, Subtract 1 since 0-> 1, 1-> 2, etc... clkctl0.pfcdiv(0).modify(|_, w| w.reset().set_bit()); // SAFETY: unsafe needed to write the bits for pfcdiv clkctl0 .pfcdiv(0) .write(|w| unsafe { w.div().bits(2 - 1).halt().clear_bit() }); while clkctl0.pfcdiv(0).read().reqflag().bit_is_set() {} // Set FRGPLLCLKDIV divider to value 12, Subtract 1 since 0-> 1, 1-> 2, etc... clkctl1.frgpllclkdiv().modify(|_, w| w.reset().set_bit()); // SAFETY: unsafe needed to write the bits for frgpllclkdiv clkctl1 .frgpllclkdiv() .write(|w| unsafe { w.div().bits(12 - 1).halt().clear_bit() }); while clkctl1.frgpllclkdiv().read().reqflag().bit_is_set() {} } } impl MultiSourceClock for MainClkConfig { fn get_clock_source_and_rate(&self, clock: &Clocks) -> Result<(Clocks, u32), ClockError> { match clock { Clocks::MainClk => { let div: u32 = if self.src == MainClkSrc::FFROdiv4 { 4 } else { 1 }; let converted_clock = Clocks::from(self.src); match ConfigurableClock::get_clock_rate(self) { Ok(_rate) => { // SAFETY: unsafe needed to take pointer to Clkctl0 // needed to calculate the clock rate from the bits written in the registers let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; if self.src == MainClkSrc::PllMain && clkctl0.syspll0ctl0().read().bypass().is_programmed_clk() { let mut temp; temp = self.freq.load(Ordering::Relaxed) * u32::from(clkctl0.syspll0ctl0().read().mult().bits()); temp = (u64::from(temp) * 18 / u64::from(clkctl0.syspll0pfd().read().pfd0().bits())) as u32; return Ok((converted_clock, temp)); } Ok((converted_clock, self.freq.load(Ordering::Relaxed) / div)) } Err(clk_err) => Err(clk_err), } } _ => Err(ClockError::ClockMismatch), } } fn set_clock_source_and_rate( &mut self, clock_src_config: &mut impl ConfigurableClock, clock_src: &Clocks, rate: u32, ) -> Result<(), ClockError> { if !clock_src_config.is_enabled() { return Err(ClockError::ClockNotEnabled); } if let Ok(c) = >::try_into(*clock_src) { // SAFETY: unsafe needed to take pointer to Clkctl0 // needed to change the clock source let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; match c { MainClkSrc::ClkIn => { self.src = MainClkSrc::ClkIn; clkctl0.mainclksela().write(|w| w.sel().sysxtal_clk()); clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); Ok(()) } // the following will yield the same result as if compared to FFROdiv4 MainClkSrc::FFRO | MainClkSrc::FFROdiv4 => match rate { div4 if div4 == (FfroFreq::Ffro60m as u32) / 4 || div4 == (FfroFreq::Ffro48m as u32) / 4 => { self.src = MainClkSrc::FFROdiv4; self.freq.store(div4, Ordering::Relaxed); clkctl0.mainclksela().write(|w| w.sel().ffro_div_4()); clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); Ok(()) } div1 if div1 == FfroFreq::Ffro60m as u32 || div1 == FfroFreq::Ffro48m as u32 => { self.src = MainClkSrc::FFRO; self.freq.store(div1, Ordering::Relaxed); clkctl0.mainclksela().write(|w| w.sel().ffro_clk()); clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); Ok(()) } _ => Err(ClockError::InvalidFrequency), }, MainClkSrc::Lposc => { if let Ok(r) = >::try_into(rate) { match r { LposcFreq::Lp1m => { self.src = MainClkSrc::Lposc; self.freq.store(rate, Ordering::Relaxed); clkctl0.mainclksela().write(|w| w.sel().lposc()); clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); Ok(()) } LposcFreq::Lp32k => Err(ClockError::InvalidFrequency), } } else { Err(ClockError::InvalidFrequency) } } MainClkSrc::SFRO => { if rate == SFRO_FREQ { self.src = MainClkSrc::SFRO; self.freq.store(rate, Ordering::Relaxed); clkctl0.mainclkselb().write(|w| w.sel().sfro_clk()); Ok(()) } else { Err(ClockError::InvalidFrequency) } } MainClkSrc::PllMain => { let r = rate; // From Section 4.6.1.1 Pll Limitations of the RT6xx User manual let pll_max = 572_000_000; let pll_min = 80_000_000; if pll_min <= r && r <= pll_max { clkctl0.mainclkselb().write(|w| w.sel().main_pll_clk()); self.src = MainClkSrc::PllMain; self.freq.store(r, Ordering::Relaxed); Ok(()) } else { Err(ClockError::InvalidFrequency) } } MainClkSrc::RTC32k => { if rate == RtcFreq::SubSecond32kHz as u32 { self.src = MainClkSrc::RTC32k; self.freq.store(rate, Ordering::Relaxed); clkctl0.mainclkselb().write(|w| w.sel().rtc_32k_clk()); Ok(()) } else { Err(ClockError::InvalidFrequency) } } } } else { Err(ClockError::ClockNotSupported) } } } impl ConfigurableClock for MainClkConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { MainClkConfig::init_main_clk(); Ok(()) } fn disable(&self) -> Result<(), ClockError> { Err(ClockError::ClockNotSupported) } fn get_clock_rate(&self) -> Result { let (_c, rate) = MainClkConfig::get_clock_source_and_rate(self, &Clocks::MainClk)?; Ok(rate) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, _freq: u32) -> Result<(), ClockError> { Err(ClockError::ClockNotSupported) } fn is_enabled(&self) -> bool { self.state == State::Enabled } } impl ConfigurableClock for ClkInConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { // External Input, no hw writes needed Ok(()) } fn disable(&self) -> Result<(), ClockError> { error!("Attempting to reset a clock input"); Err(ClockError::ClockNotSupported) } fn get_clock_rate(&self) -> Result { if self.freq.is_some() { Ok(self.freq.as_ref().unwrap().load(Ordering::Relaxed)) } else { Err(ClockError::ClockNotEnabled) } } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { self.freq.as_ref().unwrap().store(freq, Ordering::Relaxed); Ok(()) } fn is_enabled(&self) -> bool { self.state == State::Enabled } } impl RtcClkConfig { /// Register writes to initialize the RTC Clock fn init_rtc_clk() { // SAFETY: unsafe needed to take pointer to Clkctl0, Clkctl1, and RTC // needed to enable the RTC HW let cc0 = unsafe { pac::Clkctl0::steal() }; let cc1 = unsafe { pac::Clkctl1::steal() }; let r = unsafe { pac::Rtc::steal() }; // Enable the RTC peripheral clock cc1.pscctl2_set().write(|w| w.rtc_lite_clk_set().set_clock()); // Make sure the reset bit is cleared amd RTC OSC is powered up r.ctrl().modify(|_, w| w.swreset().not_in_reset().rtc_osc_pd().enable()); // set initial match value, note that with a 15 bit count-down timer this would // typically be 0x8000, but we are "doing some clever things" in time-driver.rs, // read more about it in the comments there // SAFETY: unsafe needed to write the bits r.wake().write(|w| unsafe { w.bits(0xA) }); // Enable 32K OSC cc0.osc32khzctl0().write(|w| w.ena32khz().enabled()); // enable rtc clk r.ctrl().modify(|_, w| w.rtc_en().enable()); } } impl ConfigurableClock for RtcClkConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { // should only be called once if previously disabled RtcClkConfig::init_rtc_clk(); Ok(()) } fn disable(&self) -> Result<(), ClockError> { Err(ClockError::ClockNotSupported) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { if let Ok(r) = >::try_into(freq) { // SAFETY: unsafe needed to take pointer to RTC // needed to enable the HW for the different RTC frequencies, powered down by default let rtc = unsafe { crate::pac::Rtc::steal() }; match r { RtcFreq::Default1Hz => { if rtc.ctrl().read().rtc_en().is_enable() { } else { rtc.ctrl().modify(|_r, w| w.rtc_en().enable()); } Ok(()) } RtcFreq::HighResolution1khz => { if rtc.ctrl().read().rtc1khz_en().is_enable() { } else { rtc.ctrl().modify(|_r, w| w.rtc1khz_en().enable()); } Ok(()) } RtcFreq::SubSecond32kHz => { if rtc.ctrl().read().rtc_subsec_ena().is_enable() { } else { rtc.ctrl().modify(|_r, w| w.rtc_subsec_ena().enable()); } Ok(()) } } } else { Err(ClockError::InvalidFrequency) } } // unlike the others, since this provides multiple clocks, return the fastest one fn get_clock_rate(&self) -> Result { if self.sub_second_state == State::Enabled { Ok(RtcFreq::SubSecond32kHz as u32) } else if self.wake_alarm_state == State::Enabled { Ok(RtcFreq::HighResolution1khz as u32) } else if self.state == State::Enabled { Ok(RtcFreq::Default1Hz as u32) } else { Err(ClockError::ClockNotEnabled) } } fn is_enabled(&self) -> bool { self.state == State::Enabled } } impl SysClkConfig { /// Updates the system core clock frequency, SW concept used for systick fn update_sys_core_clock(&self) {} } impl ConfigurableClock for SysOscConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { if self.state == State::Enabled { return Ok(()); } // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0, needed to modify clock HW let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; // Let CPU run on ffro for safe switching clkctl0.mainclksela().write(|w| w.sel().ffro_clk()); clkctl0.mainclksela().write(|w| w.sel().ffro_div_4()); // Power on SYSXTAL sysctl0.pdruncfg0_clr().write(|w| w.sysxtal_pd().clr_pdruncfg0()); // Enable system OSC clkctl0 .sysoscctl0() .write(|w| w.lp_enable().lp().bypass_enable().normal_mode()); delay_loop_clocks(260, SYS_OSC_DEFAULT_FREQ.into()); Ok(()) } fn disable(&self) -> Result<(), ClockError> { // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0, needed to modify clock HW let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; // Let CPU run on ffro for safe switching clkctl0.mainclksela().write(|w| w.sel().ffro_clk()); clkctl0.mainclksela().write(|w| w.sel().ffro_div_4()); // Power on SYSXTAL sysctl0.pdruncfg0_set().write(|w| w.sysxtal_pd().set_pdruncfg0()); Ok(()) } fn get_clock_rate(&self) -> Result { if self.state == State::Enabled { Ok(SYS_OSC_DEFAULT_FREQ) } else { Err(ClockError::ClockNotEnabled) } } fn is_enabled(&self) -> bool { self.state == State::Enabled } fn set_clock_rate(&mut self, _div: u8, _mult: u8, _freq: u32) -> Result<(), ClockError> { Err(ClockError::ClockNotSupported) } } /// Method to delay for a certain number of microseconds given a clock rate pub fn delay_loop_clocks(usec: u64, freq_mhz: u64) { let mut ticks = usec * freq_mhz / 1_000_000 / 4; if ticks > u64::from(u32::MAX) { ticks = u64::from(u32::MAX); } // won't panic since we check value above cortex_m::asm::delay(ticks as u32); } /// Configure the pad voltage pmc registers for all 3 vddio ranges fn set_pad_voltage_range() { // SAFETY: unsafe needed to take pointer to PNC as well as to write specific bits unsafe { let pmc = crate::pac::Pmc::steal(); // Set up IO voltages // all 3 ranges need to be 1.71-1.98V which is 01 pmc.padvrange().write(|w| { w.vddio_0range() .bits(0b01) .vddio_1range() .bits(0b01) .vddio_2range() .bits(0b01) }); } } /// Initialize AHB clock fn init_syscpuahb_clk() { // SAFETY: unsafe needed to take pointer to Clkctl0 let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; // SAFETY: unsafe needed to write the bits // Set syscpuahbclkdiv to value 2, Subtract 1 since 0-> 1, 1-> 2, etc... clkctl0.syscpuahbclkdiv().write(|w| unsafe { w.div().bits(2 - 1) }); while clkctl0.syscpuahbclkdiv().read().reqflag().bit_is_set() {} } /// `ClockOut` config pub struct ClockOutConfig { src: ClkOutSrc, div: u8, } /// `ClockOut` sources #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// `ClockOut` sources pub enum ClkOutSrc { /// No Source, reduce power consumption None, /// SFRO clock Sfro, /// External input clock ClkIn, /// Low-power oscillator Lposc, /// FFRO clock Ffro, /// Main clock MainClk, /// Main DSP clock DspMainClk, /// Main Pll clock MainPllClk, /// `SysPll` Aux0 clock Aux0PllClk, /// `SysPll` DSP clock DspPllClk, /// `SysPll` Aux1 clock Aux1PllClk, /// Audio Pll clock AudioPllClk, /// 32 `KHz` RTC RTC32k, } /// Initialize the `ClkOutConfig` impl ClockOutConfig { /// Default configuration for Clock out #[must_use] pub fn default_config() -> Self { Self { src: ClkOutSrc::None, div: 0, } } /// Enable the Clock Out output pub fn enable_and_reset(&mut self) -> Result<(), ClockError> { self.set_clkout_source_and_div(self.src, self.div)?; Ok(()) } /// Disable Clock Out output and select None as the source to conserve power pub fn disable(&mut self) -> Result<(), ClockError> { self.set_clkout_source_and_div(ClkOutSrc::None, 0)?; Ok(()) } /// Set the source of the Clock Out pin fn set_clkout_source(&mut self, src: ClkOutSrc) -> Result<(), ClockError> { // SAFETY: unsafe needed to take pointers to Clkctl1, needed to set source in HW let cc1 = unsafe { pac::Clkctl1::steal() }; match src { ClkOutSrc::None => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().none()); } ClkOutSrc::Sfro => { cc1.clkoutsel0().write(|w| w.sel().sfro_clk()); cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); } ClkOutSrc::ClkIn => { cc1.clkoutsel0().write(|w| w.sel().xtalin_clk()); cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); } ClkOutSrc::Lposc => { cc1.clkoutsel0().write(|w| w.sel().lposc()); cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); } ClkOutSrc::Ffro => { cc1.clkoutsel0().write(|w| w.sel().ffro_clk()); cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); } ClkOutSrc::MainClk => { cc1.clkoutsel0().write(|w| w.sel().main_clk()); cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); } ClkOutSrc::DspMainClk => { cc1.clkoutsel0().write(|w| w.sel().dsp_main_clk()); cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); } ClkOutSrc::MainPllClk => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().main_pll_clk()); } ClkOutSrc::Aux0PllClk => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().syspll0_aux0_pll_clk()); } ClkOutSrc::DspPllClk => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().dsp_pll_clk()); } ClkOutSrc::AudioPllClk => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().audio_pll_clk()); } ClkOutSrc::Aux1PllClk => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().syspll0_aux1_pll_clk()); } ClkOutSrc::RTC32k => { cc1.clkoutsel0().write(|w| w.sel().none()); cc1.clkoutsel1().write(|w| w.sel().rtc_clk_32khz()); } } self.src = src; Ok(()) } /// set the clock out divider /// note that 1 will be added to div when mapping to the divider /// so bits(0) -> divide by 1 /// ... /// bits(255)-> divide by 256 pub fn set_clkout_divider(&self, div: u8) -> Result<(), ClockError> { // don't wait for clock to be ready if there's no source if self.src != ClkOutSrc::None { let cc1 = unsafe { pac::Clkctl1::steal() }; cc1.clkoutdiv() .modify(|_, w| unsafe { w.div().bits(div).halt().clear_bit() }); while cc1.clkoutdiv().read().reqflag().bit_is_set() {} } Ok(()) } /// set the source and divider for the clockout pin pub fn set_clkout_source_and_div(&mut self, src: ClkOutSrc, div: u8) -> Result<(), ClockError> { self.set_clkout_source(src)?; self.set_clkout_divider(div)?; Ok(()) } } /// Using the config, enables all desired clocks to desired clock rates fn init_clock_hw(config: ClockConfig) -> Result<(), ClockError> { if let Err(e) = config.rtc.enable_and_reset() { return Err(e); } if let Err(e) = config.lposc.enable_and_reset() { return Err(e); } if let Err(e) = config.ffro.enable_and_reset() { return Err(e); } if let Err(e) = config.sfro.enable_and_reset() { return Err(e); } if let Err(e) = config.sys_osc.enable_and_reset() { return Err(e); } if let Err(e) = config.main_pll_clk.enable_and_reset() { return Err(e); } // Move FLEXSPI clock source from main clock to FFRO to avoid instruction/data fetch issue in XIP when // updating PLL and main clock. // SAFETY: unsafe needed to take pointers to Clkctl0 let cc0 = unsafe { pac::Clkctl0::steal() }; cc0.flexspifclksel().write(|w| w.sel().ffro_clk()); // Move ESPI clock source to FFRO #[cfg(feature = "_espi")] { cc0.espiclksel().write(|w| w.sel().use_48_60m()); } init_syscpuahb_clk(); if let Err(e) = config.main_clk.enable_and_reset() { return Err(e); } config.sys_clk.update_sys_core_clock(); Ok(()) } /// SAFETY: must be called exactly once at bootup pub(crate) unsafe fn init(config: ClockConfig) -> Result<(), ClockError> { init_clock_hw(config)?; // set VDDIO ranges 0-2 set_pad_voltage_range(); Ok(()) } ///Trait to expose perph clocks trait SealedSysconPeripheral { fn enable_perph_clock(); fn reset_perph(); fn disable_perph_clock(); } /// Clock and Reset control for peripherals #[allow(private_bounds)] pub trait SysconPeripheral: SealedSysconPeripheral + 'static {} /// Enables and resets peripheral `T`. /// /// # Safety /// /// Peripheral must not be in use. pub fn enable_and_reset() { T::enable_perph_clock(); T::reset_perph(); } /// Enables peripheral `T`. pub fn enable() { T::enable_perph_clock(); } /// Reset peripheral `T`. pub fn reset() { T::reset_perph(); } /// Disables peripheral `T`. /// /// # Safety /// /// Peripheral must not be in use. pub fn disable() { T::disable_perph_clock(); } macro_rules! impl_perph_clk { ($peripheral:ident, $clkctl:ident, $clkreg:ident, $rstctl:ident, $rstreg:ident, $bit:expr) => { impl SealedSysconPeripheral for crate::peripherals::$peripheral { fn enable_perph_clock() { // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 let cc1 = unsafe { pac::$clkctl::steal() }; paste! { // SAFETY: unsafe due to the use of bits() cc1.[<$clkreg _set>]().write(|w| unsafe { w.bits(1 << $bit) }); } } fn reset_perph() { // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 let rc1 = unsafe { pac::$rstctl::steal() }; paste! { // SAFETY: unsafe due to the use of bits() rc1.[<$rstreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) }); } } fn disable_perph_clock() { // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 let cc1 = unsafe { pac::$clkctl::steal() }; paste! { // SAFETY: unsafe due to the use of bits() cc1.[<$clkreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) }); } } } impl SysconPeripheral for crate::peripherals::$peripheral {} }; } // These should enabled once the relevant peripherals are implemented. // impl_perph_clk!(GPIOINTCTL, Clkctl1, pscctl2, Rstctl1, prstctl2, 30); // impl_perph_clk!(OTP, Clkctl0, pscctl0, Rstctl0, prstctl0, 17); // impl_perph_clk!(ROM_CTL_128KB, Clkctl0, pscctl0, Rstctl0, prstctl0, 2); // impl_perph_clk!(USBHS_SRAM, Clkctl0, pscctl0, Rstctl0, prstctl0, 23); impl_perph_clk!(PIMCTL, Clkctl1, pscctl2, Rstctl1, prstctl2, 31); impl_perph_clk!(ACMP, Clkctl0, pscctl1, Rstctl0, prstctl1, 15); impl_perph_clk!(ADC0, Clkctl0, pscctl1, Rstctl0, prstctl1, 16); impl_perph_clk!(CASPER, Clkctl0, pscctl0, Rstctl0, prstctl0, 9); impl_perph_clk!(CRC, Clkctl1, pscctl1, Rstctl1, prstctl1, 16); impl_perph_clk!(CTIMER0_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 0); impl_perph_clk!(CTIMER1_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 1); impl_perph_clk!(CTIMER2_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 2); impl_perph_clk!(CTIMER3_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 3); impl_perph_clk!(CTIMER4_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 4); impl_perph_clk!(DMA0, Clkctl1, pscctl1, Rstctl1, prstctl1, 23); impl_perph_clk!(DMA1, Clkctl1, pscctl1, Rstctl1, prstctl1, 24); impl_perph_clk!(DMIC0, Clkctl1, pscctl0, Rstctl1, prstctl0, 24); #[cfg(feature = "_espi")] impl_perph_clk!(ESPI, Clkctl0, pscctl1, Rstctl0, prstctl1, 7); impl_perph_clk!(FLEXCOMM0, Clkctl1, pscctl0, Rstctl1, prstctl0, 8); impl_perph_clk!(FLEXCOMM1, Clkctl1, pscctl0, Rstctl1, prstctl0, 9); impl_perph_clk!(FLEXCOMM14, Clkctl1, pscctl0, Rstctl1, prstctl0, 22); impl_perph_clk!(FLEXCOMM15, Clkctl1, pscctl0, Rstctl1, prstctl0, 23); impl_perph_clk!(FLEXCOMM2, Clkctl1, pscctl0, Rstctl1, prstctl0, 10); impl_perph_clk!(FLEXCOMM3, Clkctl1, pscctl0, Rstctl1, prstctl0, 11); impl_perph_clk!(FLEXCOMM4, Clkctl1, pscctl0, Rstctl1, prstctl0, 12); impl_perph_clk!(FLEXCOMM5, Clkctl1, pscctl0, Rstctl1, prstctl0, 13); impl_perph_clk!(FLEXCOMM6, Clkctl1, pscctl0, Rstctl1, prstctl0, 14); impl_perph_clk!(FLEXCOMM7, Clkctl1, pscctl0, Rstctl1, prstctl0, 15); impl_perph_clk!(FLEXSPI, Clkctl0, pscctl0, Rstctl0, prstctl0, 16); impl_perph_clk!(FREQME, Clkctl1, pscctl1, Rstctl1, prstctl1, 31); impl_perph_clk!(HASHCRYPT, Clkctl0, pscctl0, Rstctl0, prstctl0, 10); impl_perph_clk!(HSGPIO0, Clkctl1, pscctl1, Rstctl1, prstctl1, 0); impl_perph_clk!(HSGPIO1, Clkctl1, pscctl1, Rstctl1, prstctl1, 1); impl_perph_clk!(HSGPIO2, Clkctl1, pscctl1, Rstctl1, prstctl1, 2); impl_perph_clk!(HSGPIO3, Clkctl1, pscctl1, Rstctl1, prstctl1, 3); impl_perph_clk!(HSGPIO4, Clkctl1, pscctl1, Rstctl1, prstctl1, 4); impl_perph_clk!(HSGPIO5, Clkctl1, pscctl1, Rstctl1, prstctl1, 5); impl_perph_clk!(HSGPIO6, Clkctl1, pscctl1, Rstctl1, prstctl1, 6); impl_perph_clk!(HSGPIO7, Clkctl1, pscctl1, Rstctl1, prstctl1, 7); impl_perph_clk!(I3C0, Clkctl1, pscctl2, Rstctl1, prstctl2, 16); impl_perph_clk!(MRT0, Clkctl1, pscctl2, Rstctl1, prstctl2, 8); impl_perph_clk!(MU_A, Clkctl1, pscctl1, Rstctl1, prstctl1, 28); impl_perph_clk!(OS_EVENT, Clkctl1, pscctl0, Rstctl1, prstctl0, 27); impl_perph_clk!(POWERQUAD, Clkctl0, pscctl0, Rstctl0, prstctl0, 8); impl_perph_clk!(PUF, Clkctl0, pscctl0, Rstctl0, prstctl0, 11); impl_perph_clk!(RNG, Clkctl0, pscctl0, Rstctl0, prstctl0, 12); impl_perph_clk!(RTC, Clkctl1, pscctl2, Rstctl1, prstctl2, 7); impl_perph_clk!(SCT0, Clkctl0, pscctl0, Rstctl0, prstctl0, 24); impl_perph_clk!(SECGPIO, Clkctl0, pscctl1, Rstctl0, prstctl1, 24); impl_perph_clk!(SEMA42, Clkctl1, pscctl1, Rstctl1, prstctl1, 29); impl_perph_clk!(USBHSD, Clkctl0, pscctl0, Rstctl0, prstctl0, 21); impl_perph_clk!(USBHSH, Clkctl0, pscctl0, Rstctl0, prstctl0, 22); impl_perph_clk!(USBPHY, Clkctl0, pscctl0, Rstctl0, prstctl0, 20); impl_perph_clk!(USDHC0, Clkctl0, pscctl1, Rstctl0, prstctl1, 2); impl_perph_clk!(USDHC1, Clkctl0, pscctl1, Rstctl0, prstctl1, 3); impl_perph_clk!(UTICK0, Clkctl0, pscctl2, Rstctl0, prstctl2, 0); impl_perph_clk!(WDT0, Clkctl0, pscctl2, Rstctl0, prstctl2, 1); impl_perph_clk!(WDT1, Clkctl1, pscctl2, Rstctl1, prstctl2, 10); ================================================ FILE: embassy-imxrt/src/crc.rs ================================================ //! Cyclic Redundancy Check (CRC) use core::marker::PhantomData; use crate::clocks::{SysconPeripheral, enable_and_reset}; pub use crate::pac::crc_engine::mode::CrcPolynomial as Polynomial; use crate::{Peri, PeripheralType, peripherals}; /// CRC driver. pub struct Crc<'d> { info: Info, _config: Config, _lifetime: PhantomData<&'d ()>, } /// CRC configuration pub struct Config { /// Polynomial to be used pub polynomial: Polynomial, /// Reverse bit order of input? pub reverse_in: bool, /// 1's complement input? pub complement_in: bool, /// Reverse CRC bit order? pub reverse_out: bool, /// 1's complement CRC? pub complement_out: bool, /// CRC Seed pub seed: u32, } impl Config { /// Create a new CRC config. #[must_use] pub fn new( polynomial: Polynomial, reverse_in: bool, complement_in: bool, reverse_out: bool, complement_out: bool, seed: u32, ) -> Self { Config { polynomial, reverse_in, complement_in, reverse_out, complement_out, seed, } } } impl Default for Config { fn default() -> Self { Self { polynomial: Polynomial::CrcCcitt, reverse_in: false, complement_in: false, reverse_out: false, complement_out: false, seed: 0xffff, } } } impl<'d> Crc<'d> { /// Instantiates new CRC peripheral and initializes to default values. pub fn new(_peripheral: Peri<'d, T>, config: Config) -> Self { // enable CRC clock enable_and_reset::(); let mut instance = Self { info: T::info(), _config: config, _lifetime: PhantomData, }; instance.reconfigure(); instance } /// Reconfigured the CRC peripheral. fn reconfigure(&mut self) { self.info.regs.mode().write(|w| { w.crc_poly() .variant(self._config.polynomial) .bit_rvs_wr() .variant(self._config.reverse_in) .cmpl_wr() .variant(self._config.complement_in) .bit_rvs_sum() .variant(self._config.reverse_out) .cmpl_sum() .variant(self._config.complement_out) }); // Init CRC value self.info .regs .seed() .write(|w| unsafe { w.crc_seed().bits(self._config.seed) }); } /// Feeds a byte into the CRC peripheral. Returns the computed checksum. pub fn feed_byte(&mut self, byte: u8) -> u32 { self.info.regs.wr_data8().write(|w| unsafe { w.bits(byte) }); self.info.regs.sum().read().bits() } /// Feeds an slice of bytes into the CRC peripheral. Returns the computed checksum. pub fn feed_bytes(&mut self, bytes: &[u8]) -> u32 { let (prefix, data, suffix) = unsafe { bytes.align_to::() }; for b in prefix { self.info.regs.wr_data8().write(|w| unsafe { w.bits(*b) }); } for d in data { self.info.regs.wr_data32().write(|w| unsafe { w.bits(*d) }); } for b in suffix { self.info.regs.wr_data8().write(|w| unsafe { w.bits(*b) }); } self.info.regs.sum().read().bits() } /// Feeds a halfword into the CRC peripheral. Returns the computed checksum. pub fn feed_halfword(&mut self, halfword: u16) -> u32 { self.info.regs.wr_data16().write(|w| unsafe { w.bits(halfword) }); self.info.regs.sum().read().bits() } /// Feeds an slice of halfwords into the CRC peripheral. Returns the computed checksum. pub fn feed_halfwords(&mut self, halfwords: &[u16]) -> u32 { for halfword in halfwords { self.info.regs.wr_data16().write(|w| unsafe { w.bits(*halfword) }); } self.info.regs.sum().read().bits() } /// Feeds a words into the CRC peripheral. Returns the computed checksum. pub fn feed_word(&mut self, word: u32) -> u32 { self.info.regs.wr_data32().write(|w| unsafe { w.bits(word) }); self.info.regs.sum().read().bits() } /// Feeds an slice of words into the CRC peripheral. Returns the computed checksum. pub fn feed_words(&mut self, words: &[u32]) -> u32 { for word in words { self.info.regs.wr_data32().write(|w| unsafe { w.bits(*word) }); } self.info.regs.sum().read().bits() } } struct Info { regs: crate::pac::CrcEngine, } trait SealedInstance { fn info() -> Info; } /// CRC instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + SysconPeripheral + 'static + Send {} impl Instance for peripherals::CRC {} impl SealedInstance for peripherals::CRC { fn info() -> Info { // SAFETY: safe from single executor Info { regs: unsafe { crate::pac::CrcEngine::steal() }, } } } ================================================ FILE: embassy-imxrt/src/dma.rs ================================================ //! DMA driver. use core::future::Future; use core::pin::Pin; use core::sync::atomic::{Ordering, compiler_fence}; use core::task::{Context, Poll}; use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; use pac::dma0::channel::cfg::Periphreqen; use pac::dma0::channel::xfercfg::{Dstinc, Srcinc, Width}; use crate::clocks::enable_and_reset; use crate::interrupt::InterruptExt; use crate::peripherals::DMA0; use crate::sealed::Sealed; use crate::{BitIter, interrupt, pac, peripherals}; pub(crate) const MAX_CHUNK_SIZE: usize = 1024; #[cfg(feature = "rt")] #[interrupt] fn DMA0() { let reg = unsafe { crate::pac::Dma0::steal() }; if reg.intstat().read().activeerrint().bit() { let err = reg.errint0().read().bits(); for channel in BitIter(err) { error!("DMA error interrupt on channel {}!", channel); reg.errint0().write(|w| unsafe { w.err().bits(1 << channel) }); CHANNEL_WAKERS[channel as usize].wake(); } } if reg.intstat().read().activeint().bit() { let ia = reg.inta0().read().bits(); for channel in BitIter(ia) { reg.inta0().write(|w| unsafe { w.ia().bits(1 << channel) }); CHANNEL_WAKERS[channel as usize].wake(); } } } /// Initialize DMA controllers (DMA0 only, for now) pub(crate) unsafe fn init() { let sysctl0 = crate::pac::Sysctl0::steal(); let dmactl0 = crate::pac::Dma0::steal(); enable_and_reset::(); interrupt::DMA0.disable(); interrupt::DMA0.set_priority(interrupt::Priority::P3); dmactl0.ctrl().modify(|_, w| w.enable().set_bit()); // Set channel descriptor SRAM base address // Descriptor base must be 1K aligned let descriptor_base = core::ptr::addr_of!(DESCRIPTORS.descs) as u32; dmactl0.srambase().write(|w| w.bits(descriptor_base)); // Ensure AHB priority it highest (M4 == DMAC0) sysctl0.ahbmatrixprior().modify(|_, w| w.m4().bits(0)); interrupt::DMA0.unpend(); interrupt::DMA0.enable(); } /// DMA read. /// /// SAFETY: Slice must point to a valid location reachable by DMA. pub unsafe fn read<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: *const W, to: *mut [W]) -> Transfer<'a, C> { let count = (to.len().div_ceil(W::size() as usize) - 1) as isize; copy_inner( ch, from as *const u32, (to as *mut u32).byte_offset(count * W::size()), W::width(), count, false, true, true, ) } /// DMA write. /// /// SAFETY: Slice must point to a valid location reachable by DMA. pub unsafe fn write<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: *const [W], to: *mut W) -> Transfer<'a, C> { let count = (from.len().div_ceil(W::size() as usize) - 1) as isize; copy_inner( ch, (from as *const u32).byte_offset(count * W::size()), to as *mut u32, W::width(), count, true, false, true, ) } /// DMA copy between slices. /// /// SAFETY: Slices must point to locations reachable by DMA. pub unsafe fn copy<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: &[W], to: &mut [W]) -> Transfer<'a, C> { let from_len = from.len(); let to_len = to.len(); assert_eq!(from_len, to_len); let count = (from_len.div_ceil(W::size() as usize) - 1) as isize; copy_inner( ch, from.as_ptr().byte_offset(count * W::size()) as *const u32, to.as_mut_ptr().byte_offset(count * W::size()) as *mut u32, W::width(), count, true, true, false, ) } fn copy_inner<'a, C: Channel>( ch: Peri<'a, C>, from: *const u32, to: *mut u32, width: Width, count: isize, incr_read: bool, incr_write: bool, periph: bool, ) -> Transfer<'a, C> { let p = ch.regs(); unsafe { DESCRIPTORS.descs[ch.number() as usize].src = from as u32; DESCRIPTORS.descs[ch.number() as usize].dest = to as u32; } compiler_fence(Ordering::SeqCst); p.errint0().write(|w| unsafe { w.err().bits(1 << ch.number()) }); p.inta0().write(|w| unsafe { w.ia().bits(1 << ch.number()) }); p.channel(ch.number().into()).cfg().write(|w| { unsafe { w.chpriority().bits(0) } .periphreqen() .variant(match periph { false => Periphreqen::Disabled, true => Periphreqen::Enabled, }) .hwtrigen() .clear_bit() }); p.intenset0().write(|w| unsafe { w.inten().bits(1 << ch.number()) }); p.channel(ch.number().into()).xfercfg().write(|w| { unsafe { w.xfercount().bits(count as u16) } .cfgvalid() .set_bit() .clrtrig() .set_bit() .reload() .clear_bit() .setinta() .set_bit() .width() .variant(width) .srcinc() .variant(match incr_read { false => Srcinc::NoIncrement, true => Srcinc::WidthX1, // REVISIT: what about WidthX2 and WidthX4? }) .dstinc() .variant(match incr_write { false => Dstinc::NoIncrement, true => Dstinc::WidthX1, // REVISIT: what about WidthX2 and WidthX4? }) }); p.enableset0().write(|w| unsafe { w.ena().bits(1 << ch.number()) }); p.channel(ch.number().into()) .xfercfg() .modify(|_, w| w.swtrig().set_bit()); 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: Peri<'a, C>, } impl<'a, C: Channel> Transfer<'a, C> { pub(crate) fn new(channel: Peri<'a, C>) -> Self { Self { channel } } pub(crate) fn abort(&mut self) -> usize { let p = self.channel.regs(); p.abort0().write(|w| w.channel(self.channel.number()).set_bit()); while p.busy0().read().bsy().bits() & (1 << self.channel.number()) != 0 {} p.enableclr0() .write(|w| unsafe { w.clr().bits(1 << self.channel.number()) }); let width: u8 = p .channel(self.channel.number().into()) .xfercfg() .read() .width() .variant() .unwrap() .into(); let count = p .channel(self.channel.number().into()) .xfercfg() .read() .xfercount() .bits() + 1; usize::from(count) * usize::from(width) } } impl<'a, C: Channel> Drop for Transfer<'a, C> { fn drop(&mut self) { self.abort(); } } 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 { // Re-register the waker on each call to poll() because any calls to // wake will deregister the waker. CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker()); if self.channel.regs().active0().read().act().bits() & (1 << self.channel.number()) == 0 { Poll::Ready(()) } else { Poll::Pending } } } /// DMA channel descriptor #[derive(Copy, Clone)] #[repr(C)] struct Descriptor { reserved: u32, src: u32, dest: u32, link: u32, } impl Descriptor { const fn new() -> Self { Self { reserved: 0, src: 0, dest: 0, link: 0, } } } #[repr(align(1024))] struct Descriptors { descs: [Descriptor; CHANNEL_COUNT], } impl Descriptors { const fn new() -> Self { Self { descs: [const { Descriptor::new() }; CHANNEL_COUNT], } } } static mut DESCRIPTORS: Descriptors = Descriptors::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; pub(crate) const CHANNEL_COUNT: usize = 33; /// DMA channel interface. #[allow(private_bounds)] pub trait Channel: PeripheralType + Sealed + Into + Sized + 'static { /// Channel number. fn number(&self) -> u8; /// Channel registry block. fn regs(&self) -> &'static pac::dma0::RegisterBlock { unsafe { &*crate::pac::Dma0::ptr() } } } /// DMA word. #[allow(private_bounds)] pub trait Word: Sealed { /// Transfer width. fn width() -> Width; /// Size in bytes for the width. fn size() -> isize; } impl Sealed for u8 {} impl Word for u8 { fn width() -> Width { Width::Bit8 } fn size() -> isize { 1 } } impl Sealed for u16 {} impl Word for u16 { fn width() -> Width { Width::Bit16 } fn size() -> isize { 2 } } impl Sealed for u32 {} impl Word for u32 { fn width() -> Width { Width::Bit32 } fn size() -> isize { 4 } } /// Type erased DMA channel. pub struct AnyChannel { number: u8, } impl_peripheral!(AnyChannel); impl Sealed for AnyChannel {} impl Channel for AnyChannel { fn number(&self) -> u8 { self.number } } macro_rules! channel { ($name:ident, $num:expr) => { impl Sealed 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 { Self { number: val.number() } } } }; } channel!(DMA0_CH0, 0); channel!(DMA0_CH1, 1); channel!(DMA0_CH2, 2); channel!(DMA0_CH3, 3); channel!(DMA0_CH4, 4); channel!(DMA0_CH5, 5); channel!(DMA0_CH6, 6); channel!(DMA0_CH7, 7); channel!(DMA0_CH8, 8); channel!(DMA0_CH9, 9); channel!(DMA0_CH10, 10); channel!(DMA0_CH11, 11); channel!(DMA0_CH12, 12); channel!(DMA0_CH13, 13); channel!(DMA0_CH14, 14); channel!(DMA0_CH15, 15); channel!(DMA0_CH16, 16); channel!(DMA0_CH17, 17); channel!(DMA0_CH18, 18); channel!(DMA0_CH19, 19); channel!(DMA0_CH20, 20); channel!(DMA0_CH21, 21); channel!(DMA0_CH22, 22); channel!(DMA0_CH23, 23); channel!(DMA0_CH24, 24); channel!(DMA0_CH25, 25); channel!(DMA0_CH26, 26); channel!(DMA0_CH27, 27); channel!(DMA0_CH28, 28); channel!(DMA0_CH29, 29); channel!(DMA0_CH30, 30); channel!(DMA0_CH31, 31); channel!(DMA0_CH32, 32); ================================================ FILE: embassy-imxrt/src/flexcomm/mod.rs ================================================ //! Implements Flexcomm interface wrapper for easier usage across modules pub mod spi; pub mod uart; use paste::paste; use crate::clocks::{SysconPeripheral, enable_and_reset}; use crate::peripherals::{ FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXCOMM14, FLEXCOMM15, }; use crate::{PeripheralType, pac}; /// clock selection option #[derive(Copy, Clone, Debug)] pub enum Clock { /// SFRO Sfro, /// FFRO Ffro, /// `AUDIO_PLL` AudioPll, /// MASTER Master, /// FCn_FRG with Main clock source FcnFrgMain, /// FCn_FRG with Pll clock source FcnFrgPll, /// FCn_FRG with Sfro clock source FcnFrgSfro, /// FCn_FRG with Ffro clock source FcnFrgFfro, /// disabled None, } /// do not allow implementation of trait outside this mod mod sealed { /// trait does not get re-exported outside flexcomm mod, allowing us to safely expose only desired APIs pub trait Sealed {} } /// primary low-level flexcomm interface pub(crate) trait FlexcommLowLevel: sealed::Sealed + PeripheralType + SysconPeripheral + 'static + Send { // fetch the flexcomm register block for direct manipulation fn reg() -> &'static pac::flexcomm0::RegisterBlock; // set the clock select for this flexcomm instance and remove from reset fn enable(clk: Clock); } macro_rules! impl_flexcomm { ($($idx:expr),*) => { $( paste!{ impl sealed::Sealed for crate::peripherals::[] {} impl FlexcommLowLevel for crate::peripherals::[] { fn reg() -> &'static crate::pac::flexcomm0::RegisterBlock { // SAFETY: safe from single executor, enforce // via peripheral reference lifetime tracking unsafe { &*crate::pac::[]::ptr() } } fn enable(clk: Clock) { // SAFETY: safe from single executor let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; clkctl1.flexcomm($idx).fcfclksel().write(|w| match clk { Clock::Sfro => w.sel().sfro_clk(), Clock::Ffro => w.sel().ffro_clk(), Clock::AudioPll => w.sel().audio_pll_clk(), Clock::Master => w.sel().master_clk(), Clock::FcnFrgMain => w.sel().fcn_frg_clk(), Clock::FcnFrgPll => w.sel().fcn_frg_clk(), Clock::FcnFrgSfro => w.sel().fcn_frg_clk(), Clock::FcnFrgFfro => w.sel().fcn_frg_clk(), Clock::None => w.sel().none(), // no clock? throw an error? }); clkctl1.flexcomm($idx).frgclksel().write(|w| match clk { Clock::FcnFrgMain => w.sel().main_clk(), Clock::FcnFrgPll => w.sel().frg_pll_clk(), Clock::FcnFrgSfro => w.sel().sfro_clk(), Clock::FcnFrgFfro => w.sel().ffro_clk(), _ => w.sel().none(), // not using frg ... }); // todo: add support for frg div/mult clkctl1 .flexcomm($idx) .frgctl() .write(|w| // SAFETY: unsafe only used for .bits() call unsafe { w.mult().bits(0) }); enable_and_reset::<[]>(); } } } )* } } impl_flexcomm!(0, 1, 2, 3, 4, 5, 6, 7); // TODO: FLEXCOMM 14 is untested. Enable SPI support on FLEXCOMM14 // Add special case FLEXCOMM14 impl sealed::Sealed for crate::peripherals::FLEXCOMM14 {} impl FlexcommLowLevel for crate::peripherals::FLEXCOMM14 { fn reg() -> &'static crate::pac::flexcomm0::RegisterBlock { // SAFETY: safe from single executor, enforce // via peripheral reference lifetime tracking unsafe { &*crate::pac::Flexcomm14::ptr() } } fn enable(clk: Clock) { // SAFETY: safe from single executor let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; clkctl1.fc14fclksel().write(|w| match clk { Clock::Sfro => w.sel().sfro_clk(), Clock::Ffro => w.sel().ffro_clk(), Clock::AudioPll => w.sel().audio_pll_clk(), Clock::Master => w.sel().master_clk(), Clock::FcnFrgMain => w.sel().fcn_frg_clk(), Clock::FcnFrgPll => w.sel().fcn_frg_clk(), Clock::FcnFrgSfro => w.sel().fcn_frg_clk(), Clock::FcnFrgFfro => w.sel().fcn_frg_clk(), Clock::None => w.sel().none(), // no clock? throw an error? }); clkctl1.frg14clksel().write(|w| match clk { Clock::FcnFrgMain => w.sel().main_clk(), Clock::FcnFrgPll => w.sel().frg_pll_clk(), Clock::FcnFrgSfro => w.sel().sfro_clk(), Clock::FcnFrgFfro => w.sel().ffro_clk(), _ => w.sel().none(), // not using frg ... }); // todo: add support for frg div/mult clkctl1.frg14ctl().write(|w| // SAFETY: unsafe only used for .bits() call unsafe { w.mult().bits(0) }); enable_and_reset::(); } } // Add special case FLEXCOMM15 impl sealed::Sealed for crate::peripherals::FLEXCOMM15 {} impl FlexcommLowLevel for crate::peripherals::FLEXCOMM15 { fn reg() -> &'static crate::pac::flexcomm0::RegisterBlock { // SAFETY: safe from single executor, enforce // via peripheral reference lifetime tracking unsafe { &*crate::pac::Flexcomm15::ptr() } } fn enable(clk: Clock) { // SAFETY: safe from single executor let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; clkctl1.fc15fclksel().write(|w| match clk { Clock::Sfro => w.sel().sfro_clk(), Clock::Ffro => w.sel().ffro_clk(), Clock::AudioPll => w.sel().audio_pll_clk(), Clock::Master => w.sel().master_clk(), Clock::FcnFrgMain => w.sel().fcn_frg_clk(), Clock::FcnFrgPll => w.sel().fcn_frg_clk(), Clock::FcnFrgSfro => w.sel().fcn_frg_clk(), Clock::FcnFrgFfro => w.sel().fcn_frg_clk(), Clock::None => w.sel().none(), // no clock? throw an error? }); clkctl1.frg15clksel().write(|w| match clk { Clock::FcnFrgMain => w.sel().main_clk(), Clock::FcnFrgPll => w.sel().frg_pll_clk(), Clock::FcnFrgSfro => w.sel().sfro_clk(), Clock::FcnFrgFfro => w.sel().ffro_clk(), _ => w.sel().none(), // not using frg ... }); // todo: add support for frg div/mult clkctl1.frg15ctl().write(|w| // SAFETY: unsafe only used for .bits() call unsafe { w.mult().bits(0) }); enable_and_reset::(); } } macro_rules! into_mode { ($mode:ident, $($fc:ident),*) => { paste! { /// Sealed Mode trait trait []: FlexcommLowLevel {} /// Select mode of operation #[allow(private_bounds)] pub trait []: [] { /// Set mode of operation fn []() { Self::reg().pselid().write(|w| w.persel().[<$mode>]()); } } } $( paste!{ impl [] for crate::peripherals::$fc {} impl [] for crate::peripherals::$fc {} } )* } } into_mode!( usart, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7 ); into_mode!( spi, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXCOMM14 ); into_mode!( i2c, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXCOMM15 ); into_mode!( i2s_transmit, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7 ); into_mode!( i2s_receive, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7 ); ================================================ FILE: embassy-imxrt/src/flexcomm/spi.rs ================================================ //! Serial Peripheral Interface (SPI) driver. use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; pub use embedded_hal_1::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode, Phase, Polarity}; use paste::paste; use crate::flexcomm::Clock; use crate::gpio::{AnyPin, GpioPin as Pin}; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::iopctl::{DriveMode, DriveStrength, Inverter, IopctlPin, Pull, SlewRate}; use crate::pac::spi0::cfg::{Cpha, Cpol}; /// Driver move trait. #[allow(private_bounds)] pub trait IoMode: sealed::Sealed {} /// Blocking mode. pub struct Blocking; impl sealed::Sealed for Blocking {} impl IoMode for Blocking {} /// Async mode. pub struct Async; impl sealed::Sealed for Async {} impl IoMode for Async {} /// 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 driver. pub struct Spi<'a, M: IoMode> { info: Info, _phantom: PhantomData<&'a M>, } impl<'a> Spi<'a, Blocking> { /// Create a SPI driver in blocking mode. pub fn new_blocking( _inner: Peri<'a, T>, sck: Peri<'a, impl SckPin + 'a>, mosi: Peri<'a, impl MosiPin + 'a>, miso: Peri<'a, impl MisoPin + 'a>, config: Config, ) -> Self { sck.as_sck(); mosi.as_mosi(); miso.as_miso(); Self::new_inner(_inner, Some(sck.into()), Some(mosi.into()), Some(miso.into()), config) } /// Create a TX-only SPI driver in blocking mode. pub fn new_blocking_txonly( _inner: Peri<'a, T>, sck: Peri<'a, impl SckPin + 'a>, mosi: Peri<'a, impl MosiPin + 'a>, config: Config, ) -> Self { sck.as_sck(); mosi.as_mosi(); Self::new_inner(_inner, Some(sck.into()), Some(mosi.into()), None, config) } /// Create an RX-only SPI driver in blocking mode. pub fn new_blocking_rxonly( _inner: Peri<'a, T>, sck: Peri<'a, impl SckPin + 'a>, miso: Peri<'a, impl MisoPin + 'a>, config: Config, ) -> Self { sck.as_sck(); miso.as_miso(); Self::new_inner(_inner, Some(sck.into()), None, Some(miso.into()), config) } /// Create an internal-loopback SPI driver in blocking mode. /// /// WARNING: This is only useful for testing as it doesn't use any /// external pins. pub fn new_blocking_loopback(_inner: Peri<'a, T>, config: Config) -> Self { Self::new_inner(_inner, None, None, None, config) } } impl<'a, M: IoMode> Spi<'a, M> { /// Read data from Spi blocking execution until done. pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); for word in data.iter_mut() { // wait until we have data in the RxFIFO. while self.info.regs.fifostat().read().rxnotempty().bit_is_clear() {} self.info .regs .fifowr() .write(|w| unsafe { w.txdata().bits(*word as u16).len().bits(7) }); *word = self.info.regs.fiford().read().rxdata().bits() as u8; } }); self.flush() } /// Write data to Spi blocking execution until done. pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); for (i, word) in data.iter().enumerate() { // wait until we have space in the TxFIFO. while self.info.regs.fifostat().read().txnotfull().bit_is_clear() {} self.info.regs.fifowr().write(|w| { unsafe { w.txdata().bits(*word as u16).len().bits(7) } .rxignore() .set_bit(); if i == data.len() - 1 { w.eot().set_bit(); } w }); } }); self.flush() } /// Transfer data to SPI blocking execution until done. pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { let len = read.len().max(write.len()); critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); for i in 0..len { let wb = write.get(i).copied().unwrap_or(0); // wait until we have space in the TxFIFO. while self.info.regs.fifostat().read().txnotfull().bit_is_clear() {} self.info.regs.fifowr().write(|w| { unsafe { w.txdata().bits(wb as u16).len().bits(7) }; if i == len - 1 { w.eot().set_bit(); } w }); // wait until we have data in the RxFIFO. while self.info.regs.fifostat().read().rxnotempty().bit_is_clear() {} let rb = self.info.regs.fiford().read().rxdata().bits() as u8; if let Some(r) = read.get_mut(i) { *r = rb; } } }); self.flush() } /// Transfer data in place to SPI blocking execution until done. pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); for word in data { // wait until we have space in the TxFIFO. while self.info.regs.fifostat().read().txnotfull().bit_is_clear() {} self.info .regs .fifowr() .write(|w| unsafe { w.txdata().bits(*word as u16) }); // wait until we have data in the RxFIFO. while self.info.regs.fifostat().read().rxnotempty().bit_is_clear() {} *word = self.info.regs.fiford().read().rxdata().bits() as u8; } }); self.flush() } /// Block execution until Spi is done. pub fn flush(&mut self) -> Result<(), Error> { let regs = self.info.regs; while regs.stat().read().mstidle().bit_is_clear() {} Ok(()) } } impl<'a> Spi<'a, Async> { /// Create a SPI driver in async mode. pub fn new_async( _inner: Peri<'a, T>, sck: Peri<'a, impl SckPin + 'a>, mosi: Peri<'a, impl MosiPin + 'a>, miso: Peri<'a, impl MisoPin + 'a>, _irq: impl interrupt::typelevel::Binding> + 'a, config: Config, ) -> Self { sck.as_sck(); mosi.as_mosi(); miso.as_miso(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_inner, Some(sck.into()), Some(mosi.into()), Some(miso.into()), config) } /// Create a TX-only SPI driver in async mode. pub fn new_async_txonly( _inner: Peri<'a, T>, sck: Peri<'a, impl SckPin + 'a>, mosi: Peri<'a, impl MosiPin + 'a>, _irq: impl interrupt::typelevel::Binding> + 'a, config: Config, ) -> Self { sck.as_sck(); mosi.as_mosi(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_inner, Some(sck.into()), Some(mosi.into()), None, config) } /// Create an RX-only SPI driver in async mode. pub fn new_async_rxonly( _inner: Peri<'a, T>, sck: Peri<'a, impl SckPin + 'a>, miso: Peri<'a, impl MisoPin + 'a>, _irq: impl interrupt::typelevel::Binding> + 'a, config: Config, ) -> Self { sck.as_sck(); miso.as_miso(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_inner, Some(sck.into()), None, Some(miso.into()), config) } /// Create an internal-loopback SPI driver in async mode. /// /// WARNING: This is only useful for testing as it doesn't use any /// external pins. pub fn new_async_loopback( _inner: Peri<'a, T>, _irq: impl interrupt::typelevel::Binding> + 'a, config: Config, ) -> Self { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_inner, None, None, None, config) } /// Read data from Spi async execution until done. pub async fn async_read(&mut self, data: &mut [u8]) -> Result<(), Error> { critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); }); for word in data.iter_mut() { // wait until we have data in the RxFIFO. self.wait_for( |me| { if me.info.regs.fifostat().read().rxnotempty().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info .regs .fifointenset() .write(|w| w.rxlvl().set_bit().rxerr().set_bit()); }, ) .await; self.info .regs .fifowr() .write(|w| unsafe { w.txdata().bits(*word as u16).len().bits(7) }); *word = self.info.regs.fiford().read().rxdata().bits() as u8; } self.async_flush().await; Ok(()) } /// Write data to Spi async execution until done. pub async fn async_write(&mut self, data: &[u8]) -> Result<(), Error> { critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); }); for (i, word) in data.iter().enumerate() { // wait until we have space in the TxFIFO. self.wait_for( |me| { if me.info.regs.fifostat().read().txnotfull().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info .regs .fifointenset() .write(|w| w.txlvl().set_bit().txerr().set_bit()); }, ) .await; self.info.regs.fifowr().write(|w| { unsafe { w.txdata().bits(*word as u16).len().bits(7) } .rxignore() .set_bit(); if i == data.len() - 1 { w.eot().set_bit(); } w }); } self.async_flush().await; Ok(()) } /// Transfer data to SPI async execution until done. pub async fn async_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { let len = read.len().max(write.len()); critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); }); for i in 0..len { let wb = write.get(i).copied().unwrap_or(0); // wait until we have space in the TxFIFO. self.wait_for( |me| { if me.info.regs.fifostat().read().txnotfull().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info.regs.fifotrig().write(|w| w.txlvlena().set_bit()); me.info .regs .fifointenset() .write(|w| w.txlvl().set_bit().txerr().set_bit()); }, ) .await; self.info.regs.fifowr().write(|w| { unsafe { w.txdata().bits(wb as u16).len().bits(7) }; if i == len - 1 { w.eot().set_bit(); } w }); // wait until we have data in the RxFIFO. self.wait_for( |me| { if me.info.regs.fifostat().read().rxnotempty().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info.regs.fifotrig().write(|w| w.rxlvlena().set_bit()); me.info .regs .fifointenset() .write(|w| w.rxlvl().set_bit().rxerr().set_bit()); }, ) .await; let rb = self.info.regs.fiford().read().rxdata().bits() as u8; if let Some(r) = read.get_mut(i) { *r = rb; } } self.async_flush().await; Ok(()) } /// Transfer data in place to SPI async execution until done. pub async fn async_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { critical_section::with(|_| { self.info .regs .fifostat() .modify(|_, w| w.txerr().set_bit().rxerr().set_bit()); }); for word in data { // wait until we have space in the TxFIFO. self.wait_for( |me| { if me.info.regs.fifostat().read().txnotfull().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info .regs .fifointenset() .write(|w| w.txlvl().set_bit().txerr().set_bit()); }, ) .await; self.info .regs .fifowr() .write(|w| unsafe { w.txdata().bits(*word as u16) }); // wait until we have data in the RxFIFO. self.wait_for( |me| { if me.info.regs.fifostat().read().rxnotempty().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info .regs .fifointenset() .write(|w| w.rxlvl().set_bit().rxerr().set_bit()); }, ) .await; *word = self.info.regs.fiford().read().rxdata().bits() as u8; } self.async_flush().await; Ok(()) } /// Async flush. pub fn async_flush(&mut self) -> impl Future + use<'_, 'a> { self.wait_for( |me| { if me.info.regs.stat().read().mstidle().bit_is_set() { Poll::Ready(()) } else { Poll::Pending } }, |me| { me.info.regs.intenset().write(|w| w.mstidleen().set_bit()); }, ) } /// 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). fn wait_for(&mut self, mut f: F, mut g: G) -> impl Future + use<'_, 'a, F, U, G> where F: FnMut(&mut Self) -> Poll, G: FnMut(&mut Self), { poll_fn(move |cx| { // Register waker before checking condition, to ensure that wakes/interrupts // aren't lost between f() and g() self.info.waker.register(cx.waker()); let r = f(self); if r.is_pending() { g(self); } r }) } } impl<'a, M: IoMode> Spi<'a, M> { fn new_inner( _inner: Peri<'a, T>, sck: Option>, mosi: Option>, miso: Option>, config: Config, ) -> Self { // REVISIT: allow selecting from multiple clocks. let clk = Self::clock(&config); T::enable(clk); T::into_spi(); Self::apply_config(T::info().regs, &config); let info = T::info(); let regs = info.regs; critical_section::with(|_| match (sck.is_some(), mosi.is_some(), miso.is_some()) { (true, true, true) => { regs.fifocfg().modify(|_, w| { w.enabletx() .set_bit() .emptytx() .set_bit() .enablerx() .set_bit() .emptyrx() .set_bit() }); } (true, false, true) => { regs.fifocfg().modify(|_, w| { w.enabletx() .set_bit() .emptytx() .clear_bit() .enablerx() .set_bit() .emptyrx() .set_bit() }); } (true, true, false) => { regs.fifocfg().modify(|_, w| { w.enabletx() .set_bit() .emptytx() .set_bit() .enablerx() .clear_bit() .emptyrx() .set_bit() }); } (false, _, _) => { regs.fifocfg().modify(|_, w| { w.enabletx() .set_bit() .emptytx() .set_bit() .enablerx() .set_bit() .emptyrx() .set_bit() }); regs.cfg().modify(|_, w| w.loop_().enabled()); } _ => {} }); Self { info, _phantom: PhantomData, } } fn set_config(&mut self, config: &Config) { Self::apply_config(self.info.regs, config); } fn clock(config: &Config) -> Clock { const SFRO_CLOCK_SPEED_HZ: u32 = 16_000_000; if config.frequency > SFRO_CLOCK_SPEED_HZ { Clock::Ffro } else { Clock::Sfro } } fn clock_frequency(clock: Clock) -> u32 { match clock { Clock::Sfro => 16_000_000, Clock::Ffro => 48_000_000, _ => unreachable!(), } } fn apply_config(regs: &'static crate::pac::spi0::RegisterBlock, config: &Config) { let polarity = if config.mode.polarity == Polarity::IdleLow { Cpol::Low } else { Cpol::High }; let phase = if config.mode.phase == Phase::CaptureOnFirstTransition { Cpha::Change } else { Cpha::Capture }; let clk = Self::clock(config); let div = Self::clock_frequency(clk) / config.frequency - 1; critical_section::with(|_| { // disable SPI every time we need to modify configuration. regs.cfg().modify(|_, w| w.enable().disabled()); regs.cfg().modify(|_, w| { w.cpha() .variant(phase) .cpol() .variant(polarity) .loop_() .disabled() .master() .master_mode() }); regs.div().write(|w| unsafe { w.divval().bits(div as u16) }); regs.cfg().modify(|_, w| w.enable().enabled()); }); } } /// Spi config. #[derive(Clone)] pub struct Config { /// Frequency in Hertz. pub frequency: u32, /// SPI operating mode. pub mode: Mode, } impl Default for Config { fn default() -> Self { Self { frequency: 1_000_000, mode: MODE_0, } } } struct Info { regs: &'static crate::pac::spi0::RegisterBlock, waker: &'static AtomicWaker, } // SAFETY: safety for Send here is the same as the other accessors to // unsafe blocks: it must be done from a single executor context. // // This is a temporary workaround -- a better solution might be to // refactor Info to no longer maintain a reference to regs, but // instead look up the correct register set and then perform // operations within an unsafe block as we do for other peripherals unsafe impl Send for Info {} trait SealedInstance { fn info() -> Info; } /// Spi interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let waker = T::info().waker; let stat = T::info().regs.fifointstat().read(); if stat.perint().bit_is_set() { T::info().regs.intenclr().write(|w| w.mstidle().clear_bit_by_one()); } if stat.txlvl().bit_is_set() { T::info().regs.fifointenclr().write(|w| w.txlvl().set_bit()); } if stat.txerr().bit_is_set() { T::info().regs.fifointenclr().write(|w| w.txerr().set_bit()); } if stat.rxlvl().bit_is_set() { T::info().regs.fifointenclr().write(|w| w.rxlvl().set_bit()); } if stat.rxerr().bit_is_set() { T::info().regs.fifointenclr().write(|w| w.rxerr().set_bit()); } waker.wake(); } } /// Spi instance trait. #[allow(private_bounds)] pub trait Instance: crate::flexcomm::IntoSpi + SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this Spi instance. type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_instance { ($($n:expr),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { #[inline] fn info() -> Info { static WAKER: AtomicWaker = AtomicWaker::new(); Info { regs: unsafe { &*crate::pac::[]::ptr() }, waker: &WAKER, } } } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* } } impl_instance!(0, 1, 2, 3, 4, 5, 6, 7, 14); mod sealed { /// Seal a trait pub trait Sealed {} } impl sealed::Sealed for T {} /// IO configuration trait for Spi clk pub trait SckPin: Pin + sealed::Sealed + PeripheralType { /// convert the pin to appropriate function for Spi clk usage. fn as_sck(&self); } /// IO configuration trait for Spi mosi pub trait MosiPin: Pin + sealed::Sealed + PeripheralType { /// convert the pin to appropriate function for Spi mosi usage. fn as_mosi(&self); } /// IO configuration trait for Spi miso pub trait MisoPin: Pin + sealed::Sealed + PeripheralType { /// convert the pin to appropriate function for Spi miso usage. fn as_miso(&self); } macro_rules! impl_pin_trait { ($fcn:ident, $mode:ident, $($pin:ident, $fn:ident),*) => { paste! { $( impl [<$mode:camel Pin>] for crate::peripherals::$pin { fn [](&self) { // UM11147 table 530 pg 518 self.set_function(crate::iopctl::Function::$fn) .set_pull(Pull::None) .enable_input_buffer() .set_slew_rate(SlewRate::Standard) .set_drive_strength(DriveStrength::Normal) .disable_analog_multiplex() .set_drive_mode(DriveMode::PushPull) .set_input_inverter(Inverter::Disabled); } } )* } } } // FLEXCOMM0 impl_pin_trait!(FLEXCOMM0, sck, PIO0_0, F1, PIO3_0, F5); impl_pin_trait!(FLEXCOMM0, miso, PIO0_1, F1, PIO3_1, F5); impl_pin_trait!(FLEXCOMM0, mosi, PIO0_2, F1, PIO3_2, F5); // FLEXCOMM1 impl_pin_trait!(FLEXCOMM1, sck, PIO0_7, F1, PIO7_25, F1); impl_pin_trait!(FLEXCOMM1, miso, PIO0_8, F1, PIO7_26, F1); impl_pin_trait!(FLEXCOMM1, mosi, PIO0_9, F1, PIO7_28, F1); // FLEXCOMM2 impl_pin_trait!(FLEXCOMM2, sck, PIO0_14, F1, PIO7_29, F5); impl_pin_trait!(FLEXCOMM2, miso, PIO0_15, F1, PIO7_30, F5); impl_pin_trait!(FLEXCOMM2, mosi, PIO0_16, F1, PIO7_31, F5); // FLEXCOMM3 impl_pin_trait!(FLEXCOMM3, sck, PIO0_21, F1); impl_pin_trait!(FLEXCOMM3, miso, PIO0_22, F1); impl_pin_trait!(FLEXCOMM3, mosi, PIO0_23, F1); // FLEXCOMM4 impl_pin_trait!(FLEXCOMM4, sck, PIO0_28, F1); impl_pin_trait!(FLEXCOMM4, miso, PIO0_29, F1); impl_pin_trait!(FLEXCOMM4, mosi, PIO0_30, F1); // FLEXCOMM5 impl_pin_trait!(FLEXCOMM5, sck, PIO1_3, F1, PIO3_15, F5); impl_pin_trait!(FLEXCOMM5, miso, PIO1_4, F1, PIO3_16, F5); impl_pin_trait!(FLEXCOMM5, mosi, PIO1_5, F1, PIO3_17, F5); // FLEXCOMM6 impl_pin_trait!(FLEXCOMM6, sck, PIO3_25, F1); impl_pin_trait!(FLEXCOMM6, miso, PIO3_26, F1); impl_pin_trait!(FLEXCOMM6, mosi, PIO3_27, F1); // FLEXCOMM7 impl_pin_trait!(FLEXCOMM7, sck, PIO4_0, F1); impl_pin_trait!(FLEXCOMM7, miso, PIO4_1, F1); impl_pin_trait!(FLEXCOMM7, mosi, PIO4_2, F1); // FLEXCOMM14 impl_pin_trait!(FLEXCOMM14, sck, PIO1_11, F1); impl_pin_trait!(FLEXCOMM14, miso, PIO1_12, F1); impl_pin_trait!(FLEXCOMM14, mosi, PIO1_13, F1); /// Spi Tx DMA trait. #[allow(private_bounds)] pub trait TxDma: crate::dma::Channel {} /// Spi Rx DMA trait. #[allow(private_bounds)] pub trait RxDma: crate::dma::Channel {} macro_rules! impl_dma { ($fcn:ident, $mode:ident, $dma:ident) => { paste! { impl [<$mode Dma>] for crate::peripherals::$dma {} } }; } impl_dma!(FLEXCOMM0, Rx, DMA0_CH0); impl_dma!(FLEXCOMM0, Tx, DMA0_CH1); impl_dma!(FLEXCOMM1, Rx, DMA0_CH2); impl_dma!(FLEXCOMM1, Tx, DMA0_CH3); impl_dma!(FLEXCOMM2, Rx, DMA0_CH4); impl_dma!(FLEXCOMM2, Tx, DMA0_CH5); impl_dma!(FLEXCOMM3, Rx, DMA0_CH6); impl_dma!(FLEXCOMM3, Tx, DMA0_CH7); impl_dma!(FLEXCOMM4, Rx, DMA0_CH8); impl_dma!(FLEXCOMM4, Tx, DMA0_CH9); impl_dma!(FLEXCOMM5, Rx, DMA0_CH10); impl_dma!(FLEXCOMM5, Tx, DMA0_CH11); impl_dma!(FLEXCOMM6, Rx, DMA0_CH12); impl_dma!(FLEXCOMM6, Tx, DMA0_CH13); impl_dma!(FLEXCOMM7, Rx, DMA0_CH14); impl_dma!(FLEXCOMM7, Tx, DMA0_CH15); impl_dma!(FLEXCOMM14, Rx, DMA0_CH16); impl_dma!(FLEXCOMM14, Tx, DMA0_CH17); // ============================== impl<'d, M: IoMode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, 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, M: IoMode> embedded_hal_02::blocking::spi::Write for Spi<'d, 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, M: IoMode> embedded_hal_1::spi::ErrorType for Spi<'d, M> { type Error = Error; } impl<'d, M: IoMode> embedded_hal_1::spi::SpiBus for Spi<'d, M> { fn flush(&mut self) -> Result<(), Self::Error> { self.flush() } fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.blocking_read(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> embedded_hal_async::spi::SpiBus for Spi<'d, Async> { async fn flush(&mut self) -> Result<(), Self::Error> { self.async_flush().await; Ok(()) } async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { self.async_write(words).await } async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.async_read(words).await } async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { self.async_transfer(read, write).await } async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.async_transfer_in_place(words).await } } impl<'d, M: IoMode> SetConfig for Spi<'d, M> { type Config = Config; type ConfigError = (); fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { self.set_config(config); Ok(()) } } ================================================ FILE: embassy-imxrt/src/flexcomm/uart.rs ================================================ //! Universal Asynchronous Receiver Transmitter (UART) driver. use core::future::poll_fn; use core::marker::PhantomData; use core::sync::atomic::{AtomicU8, Ordering, compiler_fence}; use core::task::Poll; use embassy_futures::select::{Either, select}; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use paste::paste; use crate::dma::AnyChannel; use crate::flexcomm::Clock; use crate::gpio::{AnyPin, GpioPin as Pin}; use crate::interrupt::typelevel::Interrupt; use crate::iopctl::{DriveMode, DriveStrength, Inverter, IopctlPin, Pull, SlewRate}; use crate::pac::usart0::cfg::{Clkpol, Datalen, Loop, Paritysel as Parity, Stoplen, Syncen, Syncmst}; use crate::pac::usart0::ctl::Cc; use crate::sealed::Sealed; use crate::{dma, interrupt}; /// Driver move trait. #[allow(private_bounds)] pub trait Mode: Sealed {} /// Blocking mode. pub struct Blocking; impl Sealed for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl Sealed for Async {} impl Mode for Async {} /// Uart driver. pub struct Uart<'a, M: Mode> { tx: UartTx<'a, M>, rx: UartRx<'a, M>, } /// Uart TX driver. pub struct UartTx<'a, M: Mode> { info: Info, tx_dma: Option>, _phantom: PhantomData<(&'a (), M)>, } /// Uart RX driver. pub struct UartRx<'a, M: Mode> { info: Info, rx_dma: Option>, _phantom: PhantomData<(&'a (), M)>, } /// UART config #[derive(Clone, Copy)] pub struct Config { /// Baudrate of the Uart pub baudrate: u32, /// data length pub data_bits: Datalen, /// Parity pub parity: Parity, /// Stop bits pub stop_bits: Stoplen, /// Polarity of the clock pub clock_polarity: Clkpol, /// Sync/ Async operation selection pub operation: Syncen, /// Sync master/slave mode selection (only applicable in sync mode) pub sync_mode_master_select: Syncmst, /// USART continuous Clock generation enable in synchronous master mode. pub continuous_clock: Cc, /// Normal/ loopback mode pub loopback_mode: Loop, /// Clock type pub clock: Clock, } impl Default for Config { /// Default configuration for single channel sampling. fn default() -> Self { Self { baudrate: 115_200, data_bits: Datalen::Bit8, parity: Parity::NoParity, stop_bits: Stoplen::Bit1, clock_polarity: Clkpol::FallingEdge, operation: Syncen::AsynchronousMode, sync_mode_master_select: Syncmst::Slave, continuous_clock: Cc::ClockOnCharacter, loopback_mode: Loop::Normal, clock: crate::flexcomm::Clock::Sfro, } } } /// Uart Errors #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// Read error Read, /// Buffer overflow Overrun, /// Noise error Noise, /// Framing error Framing, /// Parity error Parity, /// Failure Fail, /// Invalid argument InvalidArgument, /// Uart baud rate cannot be supported with the given clock UnsupportedBaudrate, /// RX FIFO Empty RxFifoEmpty, /// TX FIFO Full TxFifoFull, /// TX Busy TxBusy, } /// shorthand for -> `Result` pub type Result = core::result::Result; impl<'a, M: Mode> UartTx<'a, M> { fn new_inner(tx_dma: Option>) -> Self { let uarttx = Self { info: T::info(), tx_dma, _phantom: PhantomData, }; uarttx.info.refcnt.fetch_add(1, Ordering::Relaxed); uarttx } } impl<'a, M: Mode> Drop for UartTx<'a, M> { fn drop(&mut self) { if self.info.refcnt.fetch_sub(1, Ordering::Relaxed) == 1 { while self.info.regs.stat().read().txidle().bit_is_clear() {} self.info.regs.fifointenclr().modify(|_, w| { w.txerr() .set_bit() .rxerr() .set_bit() .txlvl() .set_bit() .rxlvl() .set_bit() }); self.info .regs .fifocfg() .modify(|_, w| w.dmatx().clear_bit().dmarx().clear_bit()); self.info.regs.cfg().modify(|_, w| w.enable().disabled()); } } } impl<'a> UartTx<'a, Blocking> { /// Create a new UART which can only send data /// Unidirectional Uart - Tx only pub fn new_blocking(_inner: Peri<'a, T>, tx: Peri<'a, impl TxPin>, config: Config) -> Result { tx.as_tx(); let _tx = tx.into(); Uart::::init::(Some(_tx), None, None, None, config)?; Ok(Self::new_inner::(None)) } fn write_byte_internal(&mut self, byte: u8) -> Result<()> { // SAFETY: unsafe only used for .bits() self.info .regs .fifowr() .write(|w| unsafe { w.txdata().bits(u16::from(byte)) }); Ok(()) } fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { while self.info.regs.fifostat().read().txnotfull().bit_is_clear() {} // Prevent the compiler from reordering write_byte_internal() // before the loop above. compiler_fence(Ordering::Release); self.write_byte_internal(byte) } fn write_byte(&mut self, byte: u8) -> Result<()> { if self.info.regs.fifostat().read().txnotfull().bit_is_clear() { Err(Error::TxFifoFull) } else { self.write_byte_internal(byte) } } /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { for x in buf { self.blocking_write_byte(*x)?; } Ok(()) } /// Transmit the provided buffer. Non-blocking version, bails out /// if it would block. pub fn write(&mut self, buf: &[u8]) -> Result<()> { for x in buf { self.write_byte(*x)?; } Ok(()) } /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<()> { while self.info.regs.stat().read().txidle().bit_is_clear() {} Ok(()) } /// Flush UART TX. pub fn flush(&mut self) -> Result<()> { if self.info.regs.stat().read().txidle().bit_is_clear() { Err(Error::TxBusy) } else { Ok(()) } } } impl<'a, M: Mode> UartRx<'a, M> { fn new_inner(rx_dma: Option>) -> Self { let uartrx = Self { info: T::info(), rx_dma, _phantom: PhantomData, }; uartrx.info.refcnt.fetch_add(1, Ordering::Relaxed); uartrx } } impl<'a, M: Mode> Drop for UartRx<'a, M> { fn drop(&mut self) { if self.info.refcnt.fetch_sub(1, Ordering::Relaxed) == 1 { while self.info.regs.stat().read().rxidle().bit_is_clear() {} self.info.regs.fifointenclr().modify(|_, w| { w.txerr() .set_bit() .rxerr() .set_bit() .txlvl() .set_bit() .rxlvl() .set_bit() }); self.info .regs .fifocfg() .modify(|_, w| w.dmatx().clear_bit().dmarx().clear_bit()); self.info.regs.cfg().modify(|_, w| w.enable().disabled()); } } } impl<'a> UartRx<'a, Blocking> { /// Create a new blocking UART which can only receive data pub fn new_blocking(_inner: Peri<'a, T>, rx: Peri<'a, impl RxPin>, config: Config) -> Result { rx.as_rx(); let _rx = rx.into(); Uart::::init::(None, Some(_rx), None, None, config)?; Ok(Self::new_inner::(None)) } } impl UartRx<'_, Blocking> { fn read_byte_internal(&mut self) -> Result { if self.info.regs.fifostat().read().rxerr().bit_is_set() { self.info.regs.fifocfg().modify(|_, w| w.emptyrx().set_bit()); self.info.regs.fifostat().modify(|_, w| w.rxerr().set_bit()); Err(Error::Read) } else if self.info.regs.stat().read().parityerrint().bit_is_set() { self.info.regs.stat().modify(|_, w| w.parityerrint().clear_bit_by_one()); Err(Error::Parity) } else if self.info.regs.stat().read().framerrint().bit_is_set() { self.info.regs.stat().modify(|_, w| w.framerrint().clear_bit_by_one()); Err(Error::Framing) } else if self.info.regs.stat().read().rxnoiseint().bit_is_set() { self.info.regs.stat().modify(|_, w| w.rxnoiseint().clear_bit_by_one()); Err(Error::Noise) } else { let byte = self.info.regs.fiford().read().rxdata().bits() as u8; Ok(byte) } } fn read_byte(&mut self) -> Result { if self.info.regs.fifostat().read().rxnotempty().bit_is_clear() { Err(Error::RxFifoEmpty) } else { self.read_byte_internal() } } fn blocking_read_byte(&mut self) -> Result { while self.info.regs.fifostat().read().rxnotempty().bit_is_clear() {} // Prevent the compiler from reordering read_byte_internal() // before the loop above. compiler_fence(Ordering::Acquire); self.read_byte_internal() } /// Read from UART RX. Non-blocking version, bails out if it would /// block. pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { for b in buf.iter_mut() { *b = self.read_byte()?; } Ok(()) } /// Read from UART RX blocking execution until done. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { for b in buf.iter_mut() { *b = self.blocking_read_byte()?; } Ok(()) } } impl<'a, M: Mode> Uart<'a, M> { fn init( tx: Option>, rx: Option>, rts: Option>, cts: Option>, config: Config, ) -> Result<()> { T::enable(config.clock); T::into_usart(); let regs = T::info().regs; if tx.is_some() { regs.fifocfg().modify(|_, w| w.emptytx().set_bit().enabletx().enabled()); // clear FIFO error regs.fifostat().write(|w| w.txerr().set_bit()); } if rx.is_some() { regs.fifocfg().modify(|_, w| w.emptyrx().set_bit().enablerx().enabled()); // clear FIFO error regs.fifostat().write(|w| w.rxerr().set_bit()); } if rts.is_some() && cts.is_some() { regs.cfg().modify(|_, w| w.ctsen().enabled()); } Self::set_baudrate_inner::(config.baudrate, config.clock)?; Self::set_uart_config::(config); Ok(()) } fn get_fc_freq(clock: Clock) -> Result { match clock { Clock::Sfro => Ok(16_000_000), Clock::Ffro => Ok(48_000_000), // We only support Sfro and Ffro now. _ => Err(Error::InvalidArgument), } } fn set_baudrate_inner(baudrate: u32, clock: Clock) -> Result<()> { // Get source clock frequency according to clock type. let source_clock_hz = Self::get_fc_freq(clock)?; if baudrate == 0 { return Err(Error::InvalidArgument); } let regs = T::info().regs; // If synchronous master mode is enabled, only configure the BRG value. if regs.cfg().read().syncen().is_synchronous_mode() { // Master if regs.cfg().read().syncmst().is_master() { // Calculate the BRG value let brgval = (source_clock_hz / baudrate) - 1; // SAFETY: unsafe only used for .bits() regs.brg().write(|w| unsafe { w.brgval().bits(brgval as u16) }); } } else { // Smaller values of OSR can make the sampling position within a // data bit less accurate and may potentially cause more noise // errors or incorrect data. let (_, osr, brg) = (8..16).rev().fold( (u32::MAX, u32::MAX, u32::MAX), |(best_diff, best_osr, best_brg), osrval| { // Compare source_clock_hz agaist with ((osrval + 1) * baudrate) to make sure // (source_clock_hz / ((osrval + 1) * baudrate)) is not less than 0. if source_clock_hz < ((osrval + 1) * baudrate) { (best_diff, best_osr, best_brg) } else { let brgval = (source_clock_hz / ((osrval + 1) * baudrate)) - 1; // We know brgval will not be less than 0 now, it should have already been a valid u32 value, // then compare it agaist with 65535. if brgval > 65535 { (best_diff, best_osr, best_brg) } else { // Calculate the baud rate based on the BRG value let candidate = source_clock_hz / ((osrval + 1) * (brgval + 1)); // Calculate the difference between the // current baud rate and the desired baud rate let diff = (candidate as i32 - baudrate as i32).unsigned_abs(); // Check if the current calculated difference is the best so far if diff < best_diff { (diff, osrval, brgval) } else { (best_diff, best_osr, best_brg) } } } }, ); // Value over range if brg > 65535 { return Err(Error::UnsupportedBaudrate); } // SAFETY: unsafe only used for .bits() regs.osr().write(|w| unsafe { w.osrval().bits(osr as u8) }); // SAFETY: unsafe only used for .bits() regs.brg().write(|w| unsafe { w.brgval().bits(brg as u16) }); } Ok(()) } fn set_uart_config(config: Config) { let regs = T::info().regs; regs.cfg().modify(|_, w| w.enable().disabled()); regs.cfg().modify(|_, w| { w.datalen() .variant(config.data_bits) .stoplen() .variant(config.stop_bits) .paritysel() .variant(config.parity) .loop_() .variant(config.loopback_mode) .syncen() .variant(config.operation) .clkpol() .variant(config.clock_polarity) }); regs.cfg().modify(|_, w| w.enable().enabled()); } /// 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<'a, M>, UartRx<'a, 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<'a, M>, &mut UartRx<'a, M>) { (&mut self.tx, &mut self.rx) } } impl<'a> Uart<'a, Blocking> { /// Create a new blocking UART pub fn new_blocking( _inner: Peri<'a, T>, tx: Peri<'a, impl TxPin>, rx: Peri<'a, impl RxPin>, config: Config, ) -> Result { tx.as_tx(); rx.as_rx(); let tx = tx.into(); let rx = rx.into(); Self::init::(Some(tx), Some(rx), None, None, config)?; Ok(Self { tx: UartTx::new_inner::(None), rx: UartRx::new_inner::(None), }) } /// Read from UART RX blocking execution until done. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { self.rx.blocking_read(buf) } /// Read from UART RX. Non-blocking version, bails out if it would /// block. pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { self.rx.read(buf) } /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { self.tx.blocking_write(buf) } /// Transmit the provided buffer. Non-blocking version, bails out /// if it would block. pub fn write(&mut self, buf: &[u8]) -> Result<()> { self.tx.write(buf) } /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<()> { self.tx.blocking_flush() } /// Flush UART TX. pub fn flush(&mut self) -> Result<()> { self.tx.flush() } } impl<'a> UartTx<'a, Async> { /// Create a new DMA enabled UART which can only send data pub fn new_async( _inner: Peri<'a, T>, tx: Peri<'a, impl TxPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_dma: Peri<'a, impl TxDma>, config: Config, ) -> Result { tx.as_tx(); let _tx = tx.into(); Uart::::init::(Some(_tx), None, None, None, config)?; T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Ok(Self::new_inner::(Some(tx_dma.into()))) } /// Transmit the provided buffer asynchronously. pub async fn write(&mut self, buf: &[u8]) -> Result<()> { let regs = self.info.regs; // Disable DMA on completion/cancellation let _dma_guard = OnDrop::new(|| { regs.fifocfg().modify(|_, w| w.dmatx().disabled()); }); for chunk in buf.chunks(dma::MAX_CHUNK_SIZE) { regs.fifocfg().modify(|_, w| w.dmatx().enabled()); let ch = self.tx_dma.as_mut().unwrap().reborrow(); let transfer = unsafe { dma::write(ch, chunk, regs.fifowr().as_ptr() as *mut u8) }; let res = select( transfer, poll_fn(|cx| { UART_WAKERS[self.info.index].register(cx.waker()); self.info.regs.intenset().write(|w| { w.framerren() .set_bit() .parityerren() .set_bit() .rxnoiseen() .set_bit() .aberren() .set_bit() }); let stat = self.info.regs.stat().read(); self.info.regs.stat().write(|w| { w.framerrint() .clear_bit_by_one() .parityerrint() .clear_bit_by_one() .rxnoiseint() .clear_bit_by_one() .aberr() .clear_bit_by_one() }); if stat.framerrint().bit_is_set() { Poll::Ready(Err(Error::Framing)) } else if stat.parityerrint().bit_is_set() { Poll::Ready(Err(Error::Parity)) } else if stat.rxnoiseint().bit_is_set() { Poll::Ready(Err(Error::Noise)) } else if stat.aberr().bit_is_set() { Poll::Ready(Err(Error::Fail)) } else { Poll::Pending } }), ) .await; match res { Either::First(()) | Either::Second(Ok(())) => (), Either::Second(e) => return e, } } Ok(()) } /// Flush UART TX asynchronously. pub async fn flush(&mut self) -> Result<()> { self.wait_on( |me| { if me.info.regs.stat().read().txidle().bit_is_set() { Poll::Ready(Ok(())) } else { Poll::Pending } }, |me| { me.info.regs.intenset().write(|w| w.txidleen().set_bit()); }, ) .await } /// 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), { poll_fn(|cx| { // Register waker before checking condition, to ensure that wakes/interrupts // aren't lost between f() and g() UART_WAKERS[self.info.index].register(cx.waker()); let r = f(self); if r.is_pending() { g(self); } r }) .await } } impl<'a> UartRx<'a, Async> { /// Create a new DMA enabled UART which can only receive data pub fn new_async( _inner: Peri<'a, T>, rx: Peri<'a, impl RxPin>, _irq: impl interrupt::typelevel::Binding> + 'a, rx_dma: Peri<'a, impl RxDma>, config: Config, ) -> Result { rx.as_rx(); let _rx = rx.into(); Uart::::init::(None, Some(_rx), None, None, config)?; T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Ok(Self::new_inner::(Some(rx_dma.into()))) } /// Read from UART RX asynchronously. pub async fn read(&mut self, buf: &mut [u8]) -> Result<()> { let regs = self.info.regs; // Disable DMA on completion/cancellation let _dma_guard = OnDrop::new(|| { regs.fifocfg().modify(|_, w| w.dmarx().disabled()); }); for chunk in buf.chunks_mut(dma::MAX_CHUNK_SIZE) { regs.fifocfg().modify(|_, w| w.dmarx().enabled()); let ch = self.rx_dma.as_mut().unwrap().reborrow(); let transfer = unsafe { dma::read(ch, regs.fiford().as_ptr() as *const u8, chunk) }; let res = select( transfer, poll_fn(|cx| { UART_WAKERS[self.info.index].register(cx.waker()); self.info.regs.intenset().write(|w| { w.framerren() .set_bit() .parityerren() .set_bit() .rxnoiseen() .set_bit() .aberren() .set_bit() }); let stat = self.info.regs.stat().read(); self.info.regs.stat().write(|w| { w.framerrint() .clear_bit_by_one() .parityerrint() .clear_bit_by_one() .rxnoiseint() .clear_bit_by_one() .aberr() .clear_bit_by_one() }); if stat.framerrint().bit_is_set() { Poll::Ready(Err(Error::Framing)) } else if stat.parityerrint().bit_is_set() { Poll::Ready(Err(Error::Parity)) } else if stat.rxnoiseint().bit_is_set() { Poll::Ready(Err(Error::Noise)) } else if stat.aberr().bit_is_set() { Poll::Ready(Err(Error::Fail)) } else { Poll::Pending } }), ) .await; match res { Either::First(()) | Either::Second(Ok(())) => (), Either::Second(e) => return e, } } Ok(()) } } impl<'a> Uart<'a, Async> { /// Create a new DMA enabled UART pub fn new_async( _inner: Peri<'a, T>, tx: Peri<'a, impl TxPin>, rx: Peri<'a, impl RxPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_dma: Peri<'a, impl TxDma>, rx_dma: Peri<'a, impl RxDma>, config: Config, ) -> Result { tx.as_tx(); rx.as_rx(); let tx = tx.into(); let rx = rx.into(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::init::(Some(tx), Some(rx), None, None, config)?; Ok(Self { tx: UartTx::new_inner::(Some(tx_dma.into())), rx: UartRx::new_inner::(Some(rx_dma.into())), }) } /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) pub fn new_with_rtscts( _inner: Peri<'a, T>, tx: Peri<'a, impl TxPin>, rx: Peri<'a, impl RxPin>, rts: Peri<'a, impl RtsPin>, cts: Peri<'a, impl CtsPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_dma: Peri<'a, impl TxDma>, rx_dma: Peri<'a, impl RxDma>, config: Config, ) -> Result { tx.as_tx(); rx.as_rx(); rts.as_rts(); cts.as_cts(); let tx = tx.into(); let rx = rx.into(); let rts = rts.into(); let cts = cts.into(); Self::init::(Some(tx), Some(rx), Some(rts), Some(cts), config)?; Ok(Self { tx: UartTx::new_inner::(Some(tx_dma.into())), rx: UartRx::new_inner::(Some(rx_dma.into())), }) } /// Read from UART RX. pub async fn read(&mut self, buf: &mut [u8]) -> Result<()> { self.rx.read(buf).await } /// Transmit the provided buffer. pub async fn write(&mut self, buf: &[u8]) -> Result<()> { self.tx.write(buf).await } /// Flush UART TX. pub async fn flush(&mut self) -> Result<()> { self.tx.flush().await } } impl embedded_hal_02::serial::Read for UartRx<'_, Blocking> { type Error = Error; fn read(&mut self) -> core::result::Result> { let mut buf = [0; 1]; match self.read(&mut buf) { Ok(_) => Ok(buf[0]), Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_02::serial::Write for UartTx<'_, Blocking> { type Error = Error; fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { match self.write(&[word]) { Ok(_) => Ok(()), Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } fn flush(&mut self) -> core::result::Result<(), nb::Error> { match self.flush() { Ok(_) => Ok(()), Err(Error::TxBusy) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_02::blocking::serial::Write for UartTx<'_, Blocking> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { self.blocking_write(buffer) } fn bflush(&mut self) -> core::result::Result<(), Self::Error> { self.blocking_flush() } } impl embedded_hal_02::serial::Read for Uart<'_, Blocking> { type Error = Error; fn read(&mut self) -> core::result::Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } impl embedded_hal_02::serial::Write for Uart<'_, Blocking> { type Error = Error; fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { embedded_hal_02::serial::Write::write(&mut self.tx, word) } fn flush(&mut self) -> core::result::Result<(), nb::Error> { embedded_hal_02::serial::Write::flush(&mut self.tx) } } impl embedded_hal_02::blocking::serial::Write for Uart<'_, Blocking> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { self.blocking_write(buffer) } fn bflush(&mut self) -> core::result::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::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, _ => embedded_hal_nb::serial::ErrorKind::Other, } } } impl embedded_hal_nb::serial::ErrorType for UartRx<'_, Blocking> { type Error = Error; } impl embedded_hal_nb::serial::ErrorType for UartTx<'_, Blocking> { type Error = Error; } impl embedded_hal_nb::serial::ErrorType for Uart<'_, Blocking> { type Error = Error; } impl embedded_hal_nb::serial::Read for UartRx<'_, Blocking> { fn read(&mut self) -> nb::Result { let mut buf = [0; 1]; match self.read(&mut buf) { Ok(_) => Ok(buf[0]), Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_nb::serial::Write for UartTx<'_, Blocking> { fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { match self.write(&[word]) { Ok(_) => Ok(()), Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } fn flush(&mut self) -> nb::Result<(), Self::Error> { match self.flush() { Ok(_) => Ok(()), Err(Error::TxBusy) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_nb::serial::Read for Uart<'_, Blocking> { fn read(&mut self) -> core::result::Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } impl embedded_hal_nb::serial::Write for Uart<'_, Blocking> { 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) } } struct Info { regs: &'static crate::pac::usart0::RegisterBlock, index: usize, refcnt: AtomicU8, } trait SealedInstance { fn info() -> Info; fn index() -> usize; } /// UART interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } const UART_COUNT: usize = 8; static UART_WAKERS: [AtomicWaker; UART_COUNT] = [const { AtomicWaker::new() }; UART_COUNT]; impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let waker = &UART_WAKERS[T::index()]; let regs = T::info().regs; let stat = regs.intstat().read(); if stat.txidle().bit_is_set() || stat.framerrint().bit_is_set() || stat.parityerrint().bit_is_set() || stat.rxnoiseint().bit_is_set() || stat.aberrint().bit_is_set() { regs.intenclr().write(|w| { w.txidleclr() .set_bit() .framerrclr() .set_bit() .parityerrclr() .set_bit() .rxnoiseclr() .set_bit() .aberrclr() .set_bit() }); } waker.wake(); } } /// UART instance trait. #[allow(private_bounds)] pub trait Instance: crate::flexcomm::IntoUsart + SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this UART instance. type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_instance { ($($n:expr),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> Info { Info { regs: unsafe { &*crate::pac::[]::ptr() }, index: $n, refcnt: AtomicU8::new(0), } } #[inline] fn index() -> usize { $n } } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0, 1, 2, 3, 4, 5, 6, 7); impl Sealed for T {} /// io configuration trait for Uart Tx configuration pub trait TxPin: Pin + Sealed + PeripheralType { /// convert the pin to appropriate function for Uart Tx usage fn as_tx(&self); } /// io configuration trait for Uart Rx configuration pub trait RxPin: Pin + Sealed + PeripheralType { /// convert the pin to appropriate function for Uart Rx usage fn as_rx(&self); } /// io configuration trait for Uart Cts pub trait CtsPin: Pin + Sealed + PeripheralType { /// convert the pin to appropriate function for Uart Cts usage fn as_cts(&self); } /// io configuration trait for Uart Rts pub trait RtsPin: Pin + Sealed + PeripheralType { /// convert the pin to appropriate function for Uart Rts usage fn as_rts(&self); } macro_rules! impl_pin_trait { ($fcn:ident, $mode:ident, $($pin:ident, $fn:ident),*) => { paste! { $( impl [<$mode:camel Pin>] for crate::peripherals::$pin { fn [](&self) { // UM11147 table 507 pg 495 self.set_function(crate::iopctl::Function::$fn) .set_pull(Pull::None) .enable_input_buffer() .set_slew_rate(SlewRate::Standard) .set_drive_strength(DriveStrength::Normal) .disable_analog_multiplex() .set_drive_mode(DriveMode::PushPull) .set_input_inverter(Inverter::Disabled); } } )* } }; } // FLEXCOMM0 impl_pin_trait!(FLEXCOMM0, tx, PIO0_1, F1, PIO3_1, F5); impl_pin_trait!(FLEXCOMM0, rx, PIO0_2, F1, PIO3_2, F5); impl_pin_trait!(FLEXCOMM0, cts, PIO0_3, F1, PIO3_3, F5); impl_pin_trait!(FLEXCOMM0, rts, PIO0_4, F1, PIO3_4, F5); // FLEXCOMM1 impl_pin_trait!(FLEXCOMM1, tx, PIO0_8, F1, PIO7_26, F1); impl_pin_trait!(FLEXCOMM1, rx, PIO0_9, F1, PIO7_27, F1); impl_pin_trait!(FLEXCOMM1, cts, PIO0_10, F1, PIO7_28, F1); impl_pin_trait!(FLEXCOMM1, rts, PIO0_11, F1, PIO7_29, F1); // FLEXCOMM2 impl_pin_trait!(FLEXCOMM2, tx, PIO0_15, F1, PIO7_30, F5); impl_pin_trait!(FLEXCOMM2, rx, PIO0_16, F1, PIO7_31, F5); impl_pin_trait!(FLEXCOMM2, cts, PIO0_17, F1, PIO4_8, F5); impl_pin_trait!(FLEXCOMM2, rts, PIO0_18, F1); // FLEXCOMM3 impl_pin_trait!(FLEXCOMM3, tx, PIO0_22, F1); impl_pin_trait!(FLEXCOMM3, rx, PIO0_23, F1); impl_pin_trait!(FLEXCOMM3, cts, PIO0_24, F1); impl_pin_trait!(FLEXCOMM3, rts, PIO0_25, F1); // FLEXCOMM4 impl_pin_trait!(FLEXCOMM4, tx, PIO0_29, F1); impl_pin_trait!(FLEXCOMM4, rx, PIO0_30, F1); impl_pin_trait!(FLEXCOMM4, cts, PIO0_31, F1); impl_pin_trait!(FLEXCOMM4, rts, PIO1_0, F1); // FLEXCOMM5 impl_pin_trait!(FLEXCOMM5, tx, PIO1_4, F1, PIO3_16, F5); impl_pin_trait!(FLEXCOMM5, rx, PIO1_5, F1, PIO3_17, F5); impl_pin_trait!(FLEXCOMM5, cts, PIO1_6, F1, PIO3_18, F5); impl_pin_trait!(FLEXCOMM5, rts, PIO1_7, F1, PIO3_23, F5); // FLEXCOMM6 impl_pin_trait!(FLEXCOMM6, tx, PIO3_26, F1); impl_pin_trait!(FLEXCOMM6, rx, PIO3_27, F1); impl_pin_trait!(FLEXCOMM6, cts, PIO3_28, F1); impl_pin_trait!(FLEXCOMM6, rts, PIO3_29, F1); // FLEXCOMM7 impl_pin_trait!(FLEXCOMM7, tx, PIO4_1, F1); impl_pin_trait!(FLEXCOMM7, rx, PIO4_2, F1); impl_pin_trait!(FLEXCOMM7, cts, PIO4_3, F1); impl_pin_trait!(FLEXCOMM7, rts, PIO4_4, F1); /// UART Tx DMA trait. #[allow(private_bounds)] pub trait TxDma: crate::dma::Channel {} /// UART Rx DMA trait. #[allow(private_bounds)] pub trait RxDma: crate::dma::Channel {} macro_rules! impl_dma { ($fcn:ident, $mode:ident, $dma:ident) => { paste! { impl [<$mode Dma>] for crate::peripherals::$dma {} } }; } impl_dma!(FLEXCOMM0, Rx, DMA0_CH0); impl_dma!(FLEXCOMM0, Tx, DMA0_CH1); impl_dma!(FLEXCOMM1, Rx, DMA0_CH2); impl_dma!(FLEXCOMM1, Tx, DMA0_CH3); impl_dma!(FLEXCOMM2, Rx, DMA0_CH4); impl_dma!(FLEXCOMM2, Tx, DMA0_CH5); impl_dma!(FLEXCOMM3, Rx, DMA0_CH6); impl_dma!(FLEXCOMM3, Tx, DMA0_CH7); impl_dma!(FLEXCOMM4, Rx, DMA0_CH8); impl_dma!(FLEXCOMM4, Tx, DMA0_CH9); impl_dma!(FLEXCOMM5, Rx, DMA0_CH10); impl_dma!(FLEXCOMM5, Tx, DMA0_CH11); impl_dma!(FLEXCOMM6, Rx, DMA0_CH12); impl_dma!(FLEXCOMM6, Tx, DMA0_CH13); impl_dma!(FLEXCOMM7, Rx, DMA0_CH14); impl_dma!(FLEXCOMM7, Tx, DMA0_CH15); ================================================ FILE: embassy-imxrt/src/fmt.rs ================================================ #![macro_use] #![allow(unused)] use core::fmt::{Debug, Display, LowerHex}; #[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 = "defmt")] ::defmt::trace!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! debug { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::debug!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! info { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::info!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! warn { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::warn!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! error { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::error!($s $(, $x)*); #[cfg(not(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 Debug for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } impl Display for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } impl LowerHex for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } #[cfg(feature = "defmt")] impl defmt::Format for Bytes<'_> { fn format(&self, fmt: defmt::Formatter) { defmt::write!(fmt, "{:02x}", self.0) } } ================================================ FILE: embassy-imxrt/src/gpio.rs ================================================ //! GPIO use core::convert::Infallible; use core::future::Future; use core::marker::PhantomData; use core::pin::Pin as FuturePin; use core::task::{Context, Poll}; use embassy_hal_internal::interrupt::InterruptExt; use embassy_sync::waitqueue::AtomicWaker; use crate::clocks::enable_and_reset; use crate::iopctl::IopctlPin; pub use crate::iopctl::{AnyPin, DriveMode, DriveStrength, Function, Inverter, Pull, SlewRate}; use crate::sealed::Sealed; use crate::{BitIter, Peri, PeripheralType, interrupt, peripherals}; // This should be unique per IMXRT package const PORT_COUNT: usize = 8; /// Digital input or output level. #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Level { /// Logic Low Low, /// Logic 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, } } } /// Interrupt trigger levels. #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InterruptType { /// Trigger on level. Level, /// Trigger on edge. Edge, } #[cfg(feature = "rt")] #[interrupt] #[allow(non_snake_case)] fn GPIO_INTA() { irq_handler(&GPIO_WAKERS); } #[cfg(feature = "rt")] fn irq_handler(port_wakers: &[Option<&PortWaker>]) { let reg = unsafe { crate::pac::Gpio::steal() }; for (port, port_waker) in port_wakers.iter().enumerate() { let Some(port_waker) = port_waker else { continue; }; let stat = reg.intstata(port).read().bits(); for pin in BitIter(stat) { // Clear the interrupt from this pin reg.intstata(port).write(|w| unsafe { w.status().bits(1 << pin) }); // Disable interrupt from this pin reg.intena(port) .modify(|r, w| unsafe { w.int_en().bits(r.int_en().bits() & !(1 << pin)) }); let Some(waker) = port_waker.get_waker(pin as usize) else { continue; }; waker.wake(); } } } /// Initialization Logic /// Note: GPIO port clocks are initialized in the clocks module. pub(crate) fn init() { // Enable GPIO clocks enable_and_reset::(); enable_and_reset::(); enable_and_reset::(); enable_and_reset::(); enable_and_reset::(); enable_and_reset::(); enable_and_reset::(); enable_and_reset::(); // Enable INTA interrupt::GPIO_INTA.unpend(); // SAFETY: // // At this point, all GPIO interrupts are masked. No interrupts // will trigger until a pin is configured as Input, which can only // happen after initialization of the HAL unsafe { interrupt::GPIO_INTA.enable() }; } /// Input Sense mode. pub trait Sense: Sealed {} /// Sense Enabled Flex pin. /// /// This is a true flex pin as the input buffer is enabled. /// It can sense its own level when even when configured as an output pin. pub enum SenseEnabled {} impl Sealed for SenseEnabled {} impl Sense for SenseEnabled {} /// Sense Enabled Flex pin. /// /// This is **not** a true flex pin as the input buffer is disabled. /// It cannot be turned into an input and it cannot see its own state, but it consumes less power. /// Consider using a sense enabled flex pin if you need to read the pin's state or turn this into an input, /// however note that **power consumption may be increased**. pub enum SenseDisabled {} impl Sealed for SenseDisabled {} impl Sense for SenseDisabled {} /// Flex 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, S: Sense> { pin: Peri<'d, AnyPin>, _sense_mode: PhantomData, } impl Flex<'_, S> { /// Converts pin to output pin /// /// 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. pub fn set_as_output(&mut self, mode: DriveMode, strength: DriveStrength, slew_rate: SlewRate) { self.pin .set_pull(Pull::None) .set_drive_mode(mode) .set_drive_strength(strength) .set_slew_rate(slew_rate); self.pin.block().dirset(self.pin.port()).write(|w| // SAFETY: Writing a 0 to bits in this register has no effect, // however PAC has it marked unsafe due to using the bits() method. // There is not currently a "safe" method for setting a single-bit. unsafe { w.dirsetp().bits(1 << self.pin.pin()) }); } /// Set high pub fn set_high(&mut self) { self.pin.block().set(self.pin.port()).write(|w| // SAFETY: Writing a 0 to bits in this register has no effect, // however PAC has it marked unsafe due to using the bits() method. // There is not currently a "safe" method for setting a single-bit. unsafe { w.setp().bits(1 << self.pin.pin()) }); } /// Set low pub fn set_low(&mut self) { self.pin.block().clr(self.pin.port()).write(|w| // SAFETY: Writing a 0 to bits in this register has no effect, // however PAC has it marked unsafe due to using the bits() method. // There is not currently a "safe" method for setting a single-bit. unsafe { w.clrp().bits(1 << self.pin.pin()) }); } /// Set level pub fn set_level(&mut self, level: Level) { match level { Level::High => self.set_high(), Level::Low => self.set_low(), } } /// Is the output level high? #[must_use] pub fn is_set_high(&self) -> bool { !self.is_set_low() } /// Is the output level low? #[must_use] pub fn is_set_low(&self) -> bool { (self.pin.block().set(self.pin.port()).read().setp().bits() & (1 << self.pin.pin())) == 0 } /// Toggle pub fn toggle(&mut self) { self.pin.block().not(self.pin.port()).write(|w| // SAFETY: Writing a 0 to bits in this register has no effect, // however PAC has it marked unsafe due to using the bits() method. // There is not currently a "safe" method for setting a single-bit. unsafe { w.notp().bits(1 << self.pin.pin()) }); } } impl Drop for Flex<'_, S> { #[inline] fn drop(&mut self) { critical_section::with(|_| { self.pin.reset(); }); } } impl<'d> Flex<'d, SenseEnabled> { /// New flex pin. pub fn new_with_sense(pin: Peri<'d, impl GpioPin>) -> Self { pin.set_function(Function::F0) .disable_analog_multiplex() .enable_input_buffer(); Self { pin: pin.into(), _sense_mode: PhantomData::, } } /// Converts pin to input pin pub fn set_as_input(&mut self, pull: Pull, inverter: Inverter) { self.pin.set_pull(pull).set_input_inverter(inverter); self.pin.block().dirclr(self.pin.port()).write(|w| // SAFETY: Writing a 0 to bits in this register has no effect, // however PAC has it marked unsafe due to using the bits() method. // There is not currently a "safe" method for setting a single-bit. unsafe { w.dirclrp().bits(1 << self.pin.pin()) }); } /// Converts pin to special function pin /// # Safety /// Unsafe to require justifying change from default to a special function /// pub unsafe fn set_as_special_function(&mut self, func: Function) { self.pin.set_function(func); } /// Is high? #[must_use] pub fn is_high(&self) -> bool { !self.is_low() } /// Is low? #[must_use] pub fn is_low(&self) -> bool { self.pin.block().b(self.pin.port()).b_(self.pin.pin()).read() == 0 } /// Current level #[must_use] 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) { InputFuture::new(self.pin.reborrow(), InterruptType::Level, Level::High).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(), InterruptType::Level, Level::Low).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(), InterruptType::Edge, Level::High).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(), InterruptType::Edge, Level::Low).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) { if self.is_high() { InputFuture::new(self.pin.reborrow(), InterruptType::Edge, Level::Low).await; } else { InputFuture::new(self.pin.reborrow(), InterruptType::Edge, Level::High).await; } } /// Return a new Flex pin instance with level sensing disabled. /// /// Consumes less power than a flex pin with sensing enabled. #[must_use] pub fn disable_sensing(self) -> Flex<'d, SenseDisabled> { // Cloning the pin is ok since we consume self immediately let new_pin = unsafe { self.pin.clone_unchecked() }; drop(self); Flex::::new(new_pin) } } impl<'d> Flex<'d, SenseDisabled> { /// New flex pin. pub fn new(pin: Peri<'d, impl GpioPin>) -> Self { pin.set_function(Function::F0) .disable_analog_multiplex() .disable_input_buffer(); Self { pin: pin.into(), _sense_mode: PhantomData::, } } /// Return a new Flex pin instance with level sensing enabled. #[must_use] pub fn enable_sensing(self) -> Flex<'d, SenseEnabled> { // Cloning the pin is ok since we consume self immediately let new_pin = unsafe { self.pin.clone_unchecked() }; drop(self); Flex::new_with_sense(new_pin) } } /// Input pin pub struct Input<'d> { // When Input is dropped, Flex's drop() will make sure the pin is reset to its default state. pin: Flex<'d, SenseEnabled>, } impl<'d> Input<'d> { /// New input pin pub fn new(pin: Peri<'d, impl GpioPin>, pull: Pull, inverter: Inverter) -> Self { let mut pin = Flex::new_with_sense(pin); pin.set_as_input(pull, inverter); Self { pin } } /// Is high? #[must_use] pub fn is_high(&self) -> bool { self.pin.is_high() } /// Is low? #[must_use] pub fn is_low(&self) -> bool { self.pin.is_low() } /// Input level #[must_use] 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; } } #[must_use = "futures do nothing unless you `.await` or poll them"] struct InputFuture<'d> { pin: Peri<'d, AnyPin>, } impl<'d> InputFuture<'d> { fn new(pin: Peri<'d, impl GpioPin>, int_type: InterruptType, level: Level) -> Self { critical_section::with(|_| { // Clear any existing pending interrupt on this pin pin.block() .intstata(pin.port()) .write(|w| unsafe { w.status().bits(1 << pin.pin()) }); /* Pin interrupt configuration */ pin.block().intedg(pin.port()).modify(|r, w| match int_type { InterruptType::Edge => unsafe { w.bits(r.bits() | (1 << pin.pin())) }, InterruptType::Level => unsafe { w.bits(r.bits() & !(1 << pin.pin())) }, }); pin.block().intpol(pin.port()).modify(|r, w| match level { Level::High => unsafe { w.bits(r.bits() & !(1 << pin.pin())) }, Level::Low => unsafe { w.bits(r.bits() | (1 << pin.pin())) }, }); // Enable pin interrupt on GPIO INT A pin.block() .intena(pin.port()) .modify(|r, w| unsafe { w.int_en().bits(r.int_en().bits() | (1 << pin.pin())) }); }); Self { pin: pin.into() } } } impl Future for InputFuture<'_> { 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. if self.pin.port() >= GPIO_WAKERS.len() { panic!("Invalid GPIO port index {}", self.pin.port()); } let port_waker = GPIO_WAKERS[self.pin.port()]; if port_waker.is_none() { panic!("Waker not present for GPIO port {}", self.pin.port()); } let waker = port_waker.unwrap().get_waker(self.pin.pin()); if waker.is_none() { panic!( "Waker not present for GPIO pin {}, port {}", self.pin.pin(), self.pin.port() ); } waker.unwrap().register(cx.waker()); // Double check that the pin interrut has been disabled by IRQ handler if self.pin.block().intena(self.pin.port()).read().bits() & (1 << self.pin.pin()) == 0 { Poll::Ready(()) } else { Poll::Pending } } } /// Output pin /// Cannot be set as an input and cannot read its own pin state! /// Consider using a Flex pin if you want that functionality, at the cost of higher power consumption. pub struct Output<'d> { // When Output is dropped, Flex's drop() will make sure the pin is reset to its default state. pin: Flex<'d, SenseDisabled>, } impl<'d> Output<'d> { /// New output pin pub fn new( pin: Peri<'d, impl GpioPin>, initial_output: Level, mode: DriveMode, strength: DriveStrength, slew_rate: SlewRate, ) -> Self { let mut pin = Flex::new(pin); pin.set_level(initial_output); pin.set_as_output(mode, strength, slew_rate); Self { pin } } /// Set high pub fn set_high(&mut self) { self.pin.set_high(); } /// Set low pub fn set_low(&mut self) { self.pin.set_low(); } /// Toggle pub fn toggle(&mut self) { self.pin.toggle(); } /// Set level pub fn set_level(&mut self, level: Level) { self.pin.set_level(level); } /// Is set high? #[must_use] pub fn is_set_high(&self) -> bool { self.pin.is_set_high() } /// Is set low? #[must_use] pub fn is_set_low(&self) -> bool { self.pin.is_set_low() } } trait SealedPin: IopctlPin { fn pin_port(&self) -> usize; fn port(&self) -> usize { self.pin_port() / 32 } fn pin(&self) -> usize { self.pin_port() % 32 } fn block(&self) -> crate::pac::Gpio { // SAFETY: Assuming GPIO pin specific registers are only accessed through this HAL, // this is safe because the HAL ensures ownership or exclusive mutable references // to pins. unsafe { crate::pac::Gpio::steal() } } } /// GPIO pin trait. #[allow(private_bounds)] pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { /// Type-erase the pin. fn degrade(self) -> AnyPin { // SAFETY: This is only called within the GpioPin trait, which is only // implemented within this module on valid pin peripherals and thus // has been verified to be correct. unsafe { AnyPin::steal(self.port() as u8, self.pin() as u8) } } } impl SealedPin for AnyPin { fn pin_port(&self) -> usize { self.pin_port() } } impl GpioPin for AnyPin {} macro_rules! impl_pin { ($pin_periph:ident, $pin_port:expr, $pin_no:expr) => { impl SealedPin for crate::peripherals::$pin_periph { fn pin_port(&self) -> usize { $pin_port * 32 + $pin_no } } impl GpioPin for crate::peripherals::$pin_periph {} impl From for AnyPin { fn from(value: crate::peripherals::$pin_periph) -> Self { value.degrade() } } }; } /// Container for pin wakers struct PortWaker { offset: usize, wakers: &'static [AtomicWaker], } impl PortWaker { fn get_waker(&self, pin: usize) -> Option<&AtomicWaker> { self.wakers.get(pin - self.offset) } } macro_rules! define_port_waker { ($name:ident, $start:expr, $end:expr) => { mod $name { static PIN_WAKERS: [super::AtomicWaker; $end - $start + 1] = [const { super::AtomicWaker::new() }; $end - $start + 1]; pub static WAKER: super::PortWaker = super::PortWaker { offset: $start, wakers: &PIN_WAKERS, }; } }; } // GPIO port 0 define_port_waker!(port0_waker, 0, 31); impl_pin!(PIO0_0, 0, 0); impl_pin!(PIO0_1, 0, 1); impl_pin!(PIO0_2, 0, 2); impl_pin!(PIO0_3, 0, 3); impl_pin!(PIO0_4, 0, 4); impl_pin!(PIO0_5, 0, 5); impl_pin!(PIO0_6, 0, 6); impl_pin!(PIO0_7, 0, 7); impl_pin!(PIO0_8, 0, 8); impl_pin!(PIO0_9, 0, 9); impl_pin!(PIO0_10, 0, 10); impl_pin!(PIO0_11, 0, 11); impl_pin!(PIO0_12, 0, 12); impl_pin!(PIO0_13, 0, 13); impl_pin!(PIO0_14, 0, 14); impl_pin!(PIO0_15, 0, 15); impl_pin!(PIO0_16, 0, 16); impl_pin!(PIO0_17, 0, 17); impl_pin!(PIO0_18, 0, 18); impl_pin!(PIO0_19, 0, 19); impl_pin!(PIO0_20, 0, 20); impl_pin!(PIO0_21, 0, 21); impl_pin!(PIO0_22, 0, 22); impl_pin!(PIO0_23, 0, 23); impl_pin!(PIO0_24, 0, 24); impl_pin!(PIO0_25, 0, 25); impl_pin!(PIO0_26, 0, 26); impl_pin!(PIO0_27, 0, 27); impl_pin!(PIO0_28, 0, 28); impl_pin!(PIO0_29, 0, 29); impl_pin!(PIO0_30, 0, 30); impl_pin!(PIO0_31, 0, 31); // GPIO port 1 define_port_waker!(port1_waker, 0, 31); impl_pin!(PIO1_0, 1, 0); impl_pin!(PIO1_1, 1, 1); impl_pin!(PIO1_2, 1, 2); impl_pin!(PIO1_3, 1, 3); impl_pin!(PIO1_4, 1, 4); impl_pin!(PIO1_5, 1, 5); impl_pin!(PIO1_6, 1, 6); impl_pin!(PIO1_7, 1, 7); impl_pin!(PIO1_8, 1, 8); impl_pin!(PIO1_9, 1, 9); impl_pin!(PIO1_10, 1, 10); impl_pin!(PIO1_11, 1, 11); impl_pin!(PIO1_12, 1, 12); impl_pin!(PIO1_13, 1, 13); impl_pin!(PIO1_14, 1, 14); impl_pin!(PIO1_15, 1, 15); impl_pin!(PIO1_16, 1, 16); impl_pin!(PIO1_17, 1, 17); impl_pin!(PIO1_18, 1, 18); impl_pin!(PIO1_19, 1, 19); impl_pin!(PIO1_20, 1, 20); impl_pin!(PIO1_21, 1, 21); impl_pin!(PIO1_22, 1, 22); impl_pin!(PIO1_23, 1, 23); impl_pin!(PIO1_24, 1, 24); impl_pin!(PIO1_25, 1, 25); impl_pin!(PIO1_26, 1, 26); impl_pin!(PIO1_27, 1, 27); impl_pin!(PIO1_28, 1, 28); impl_pin!(PIO1_29, 1, 29); impl_pin!(PIO1_30, 1, 30); impl_pin!(PIO1_31, 1, 31); // GPIO port 2 define_port_waker!(port2_waker, 0, 31); impl_pin!(PIO2_0, 2, 0); impl_pin!(PIO2_1, 2, 1); impl_pin!(PIO2_2, 2, 2); impl_pin!(PIO2_3, 2, 3); impl_pin!(PIO2_4, 2, 4); impl_pin!(PIO2_5, 2, 5); impl_pin!(PIO2_6, 2, 6); impl_pin!(PIO2_7, 2, 7); impl_pin!(PIO2_8, 2, 8); impl_pin!(PIO2_9, 2, 9); impl_pin!(PIO2_10, 2, 10); impl_pin!(PIO2_11, 2, 11); impl_pin!(PIO2_12, 2, 12); impl_pin!(PIO2_13, 2, 13); impl_pin!(PIO2_14, 2, 14); impl_pin!(PIO2_15, 2, 15); impl_pin!(PIO2_16, 2, 16); impl_pin!(PIO2_17, 2, 17); impl_pin!(PIO2_18, 2, 18); impl_pin!(PIO2_19, 2, 19); impl_pin!(PIO2_20, 2, 20); impl_pin!(PIO2_21, 2, 21); impl_pin!(PIO2_22, 2, 22); impl_pin!(PIO2_23, 2, 23); impl_pin!(PIO2_24, 2, 24); impl_pin!(PIO2_25, 2, 25); impl_pin!(PIO2_26, 2, 26); impl_pin!(PIO2_27, 2, 27); impl_pin!(PIO2_28, 2, 28); impl_pin!(PIO2_29, 2, 29); impl_pin!(PIO2_30, 2, 30); impl_pin!(PIO2_31, 2, 31); // GPIO port 3 define_port_waker!(port3_waker, 0, 31); impl_pin!(PIO3_0, 3, 0); impl_pin!(PIO3_1, 3, 1); impl_pin!(PIO3_2, 3, 2); impl_pin!(PIO3_3, 3, 3); impl_pin!(PIO3_4, 3, 4); impl_pin!(PIO3_5, 3, 5); impl_pin!(PIO3_6, 3, 6); impl_pin!(PIO3_7, 3, 7); impl_pin!(PIO3_8, 3, 8); impl_pin!(PIO3_9, 3, 9); impl_pin!(PIO3_10, 3, 10); impl_pin!(PIO3_11, 3, 11); impl_pin!(PIO3_12, 3, 12); impl_pin!(PIO3_13, 3, 13); impl_pin!(PIO3_14, 3, 14); impl_pin!(PIO3_15, 3, 15); impl_pin!(PIO3_16, 3, 16); impl_pin!(PIO3_17, 3, 17); impl_pin!(PIO3_18, 3, 18); impl_pin!(PIO3_19, 3, 19); impl_pin!(PIO3_20, 3, 20); impl_pin!(PIO3_21, 3, 21); impl_pin!(PIO3_22, 3, 22); impl_pin!(PIO3_23, 3, 23); impl_pin!(PIO3_24, 3, 24); impl_pin!(PIO3_25, 3, 25); impl_pin!(PIO3_26, 3, 26); impl_pin!(PIO3_27, 3, 27); impl_pin!(PIO3_28, 3, 28); impl_pin!(PIO3_29, 3, 29); impl_pin!(PIO3_30, 3, 30); impl_pin!(PIO3_31, 3, 31); // GPIO port 4 define_port_waker!(port4_waker, 0, 10); impl_pin!(PIO4_0, 4, 0); impl_pin!(PIO4_1, 4, 1); impl_pin!(PIO4_2, 4, 2); impl_pin!(PIO4_3, 4, 3); impl_pin!(PIO4_4, 4, 4); impl_pin!(PIO4_5, 4, 5); impl_pin!(PIO4_6, 4, 6); impl_pin!(PIO4_7, 4, 7); impl_pin!(PIO4_8, 4, 8); impl_pin!(PIO4_9, 4, 9); impl_pin!(PIO4_10, 4, 10); // GPIO port 7 define_port_waker!(port7_waker, 24, 31); impl_pin!(PIO7_24, 7, 24); impl_pin!(PIO7_25, 7, 25); impl_pin!(PIO7_26, 7, 26); impl_pin!(PIO7_27, 7, 27); impl_pin!(PIO7_28, 7, 28); impl_pin!(PIO7_29, 7, 29); impl_pin!(PIO7_30, 7, 30); impl_pin!(PIO7_31, 7, 31); static GPIO_WAKERS: [Option<&PortWaker>; PORT_COUNT] = [ Some(&port0_waker::WAKER), Some(&port1_waker::WAKER), Some(&port2_waker::WAKER), Some(&port3_waker::WAKER), Some(&port4_waker::WAKER), None, None, Some(&port7_waker::WAKER), ]; impl embedded_hal_02::digital::v2::InputPin for Flex<'_, SenseEnabled> { type Error = Infallible; #[inline] fn is_high(&self) -> Result { Ok(self.is_high()) } #[inline] fn is_low(&self) -> Result { Ok(self.is_low()) } } impl embedded_hal_02::digital::v2::OutputPin for Flex<'_, S> { 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 embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'_, SenseEnabled> { #[inline] fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } #[inline] fn is_set_low(&self) -> Result { Ok(self.is_set_low()) } } impl embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'_, S> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { self.toggle(); Ok(()) } } impl embedded_hal_02::digital::v2::InputPin for Input<'_> { type Error = Infallible; #[inline] fn is_high(&self) -> Result { Ok(self.is_high()) } #[inline] fn is_low(&self) -> Result { Ok(self.is_low()) } } impl embedded_hal_02::digital::v2::OutputPin for Output<'_> { 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 embedded_hal_02::digital::v2::StatefulOutputPin for Output<'_> { #[inline] fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } #[inline] fn is_set_low(&self) -> Result { Ok(self.is_set_low()) } } impl embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'_> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { self.toggle(); Ok(()) } } impl embedded_hal_1::digital::ErrorType for Flex<'_, S> { type Error = Infallible; } impl embedded_hal_1::digital::InputPin for Flex<'_, SenseEnabled> { #[inline] fn is_high(&mut self) -> Result { // Dereference of self is used here and a few other places to // access the correct method (since different types/traits // share method names) Ok((*self).is_high()) } #[inline] fn is_low(&mut self) -> Result { Ok((*self).is_low()) } } impl embedded_hal_1::digital::OutputPin for Flex<'_, S> { #[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 embedded_hal_1::digital::StatefulOutputPin for Flex<'_, SenseEnabled> { #[inline] fn is_set_high(&mut self) -> Result { Ok((*self).is_set_high()) } #[inline] fn is_set_low(&mut self) -> Result { Ok((*self).is_set_low()) } } impl<'d> embedded_hal_async::digital::Wait for Flex<'d, SenseEnabled> { #[inline] async fn wait_for_high(&mut self) -> Result<(), Self::Error> { self.wait_for_high().await; Ok(()) } #[inline] async fn wait_for_low(&mut self) -> Result<(), Self::Error> { self.wait_for_low().await; Ok(()) } #[inline] async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { self.wait_for_rising_edge().await; Ok(()) } #[inline] async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { self.wait_for_falling_edge().await; Ok(()) } #[inline] async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { self.wait_for_any_edge().await; Ok(()) } } impl embedded_hal_1::digital::ErrorType for Input<'_> { type Error = Infallible; } impl embedded_hal_1::digital::InputPin for Input<'_> { #[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_async::digital::Wait for Input<'d> { #[inline] async fn wait_for_high(&mut self) -> Result<(), Self::Error> { self.wait_for_high().await; Ok(()) } #[inline] async fn wait_for_low(&mut self) -> Result<(), Self::Error> { self.wait_for_low().await; Ok(()) } #[inline] async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { self.wait_for_rising_edge().await; Ok(()) } #[inline] async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { self.wait_for_falling_edge().await; Ok(()) } #[inline] async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { self.wait_for_any_edge().await; Ok(()) } } impl embedded_hal_1::digital::ErrorType for Output<'_> { type Error = Infallible; } impl embedded_hal_1::digital::OutputPin for Output<'_> { #[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 embedded_hal_1::digital::StatefulOutputPin for Output<'_> { #[inline] fn is_set_high(&mut self) -> Result { Ok((*self).is_set_high()) } #[inline] fn is_set_low(&mut self) -> Result { Ok((*self).is_set_low()) } } ================================================ FILE: embassy-imxrt/src/iopctl.rs ================================================ //! IO Pad Controller (IOPCTL) //! //! Also known as IO Pin Configuration (IOCON) use crate::pac::{Iopctl, iopctl}; // A generic pin of any type. // // The actual pin type used here is arbitrary, // as all PioM_N types provide the same methods. // // Merely need some pin type to cast a raw pointer // to in order to access the provided methods. #[allow(non_camel_case_types)] type PioM_N = iopctl::Pio0_0; /// Pin function number. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Function { /// Function 0 F0, /// Function 1 F1, /// Function 2 F2, /// Function 3 F3, /// Function 4 F4, /// Function 5 F5, /// Function 6 F6, /// Function 7 F7, /// Function 8 F8, } /// Internal pull-up/down resistors on a pin. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Pull { /// No pull-up or pull-down resistor selected None, /// Pull-up resistor Up, /// Pull-down resistor Down, } /// Pin slew rate. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SlewRate { /// Standard slew rate Standard, /// Slow slew rate Slow, } /// Output drive strength of a pin. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum DriveStrength { /// Normal Normal, /// Full Full, } /// Output drive mode of a pin. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum DriveMode { /// Push-Pull PushPull, /// Pseudo Open-Drain OpenDrain, } /// Input inverter of a pin. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Inverter { /// No inverter Disabled, /// Enable input inverter on the input port. A low signal will be /// seen as a high signal by the pin. Enabled, } trait SealedPin {} trait ToAnyPin: SealedPin { #[inline] fn to_raw(port: u8, pin: u8) -> AnyPin { // SAFETY: This is safe since this is only called from within the module, // where the port and pin numbers have been verified to be correct. unsafe { AnyPin::steal(port, pin) } } } trait ToFC15Pin: SealedPin { #[inline] fn to_raw(pin: u8) -> FC15Pin { // SAFETY: This is safe since this is only called from within the module, // where the port and pin numbers have been verified to be correct. unsafe { FC15Pin::steal(pin) } } } /// A pin that can be configured via iopctl. #[allow(private_bounds)] pub trait IopctlPin: SealedPin { /// Sets the function number of a pin. /// /// This number corresponds to a specific function that the pin supports. /// /// Typically, function 0 corresponds to GPIO while other numbers correspond to a special function. /// /// See Section 7.5.3 in reference manual for list of pins and their supported functions. fn set_function(&self, function: Function) -> &Self; /// Enables either a pull-up or pull-down resistor on a pin. /// /// Setting this to [`Pull::None`] will disable the resistor. fn set_pull(&self, pull: Pull) -> &Self; /// Enables the input buffer of a pin. /// /// This must be enabled for any pin acting as an input, /// and some peripheral pins acting as output may need this enabled as well. /// /// If there is any doubt, it is best to enable the input buffer. /// /// See Section 7.4.2.3 of reference manual. fn enable_input_buffer(&self) -> &Self; /// Disables the input buffer of a pin. fn disable_input_buffer(&self) -> &Self; /// Sets the slew rate of a pin. /// /// This controls the speed at which a pin can toggle, /// which is voltage and load dependent. fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self; /// Sets the output drive strength of a pin. /// /// A drive strength of [`DriveStrength::Full`] has twice the /// high and low drive capability of the [`DriveStrength::Normal`] setting. fn set_drive_strength(&self, strength: DriveStrength) -> &Self; /// Enables the analog multiplexer of a pin. /// /// This must be called to allow analog functionalities of a pin. /// /// To protect the analog input, [`IopctlPin::set_function`] should be /// called with [`Function::F0`] to disable digital functions. /// /// Additionally, [`IopctlPin::disable_input_buffer`] and [`IopctlPin::set_pull`] /// with [`Pull::None`] should be called. fn enable_analog_multiplex(&self) -> &Self; /// Disables the analog multiplexer of a pin. fn disable_analog_multiplex(&self) -> &Self; /// Sets the ouput drive mode of a pin. /// /// A pin configured as [`DriveMode::OpenDrain`] actually operates in /// a "pseudo" open-drain mode which is somewhat different than true open-drain. /// /// See Section 7.4.2.7 of reference manual. fn set_drive_mode(&self, mode: DriveMode) -> &Self; /// Sets the input inverter of an input pin. /// /// Setting this to [`Inverter::Enabled`] will invert /// the input signal. fn set_input_inverter(&self, inverter: Inverter) -> &Self; /// Returns a pin to its reset state. fn reset(&self) -> &Self; } /// Represents a pin peripheral created at run-time from given port and pin numbers. pub struct AnyPin { pin_port: u8, reg: &'static PioM_N, } impl AnyPin { /// Creates a pin from raw port and pin numbers which can then be configured. /// /// This should ONLY be called when there is no other choice /// (e.g. from a type-erased GPIO pin). /// /// Otherwise, pin peripherals should be configured directly. /// /// # Safety /// /// The caller MUST ensure valid port and pin numbers are provided, /// and that multiple instances of [`AnyPin`] with the same port /// and pin combination are not being used simultaneously. /// /// Failure to uphold these requirements will result in undefined behavior. /// /// See Table 297 in reference manual for a list of valid /// pin and port number combinations. #[must_use] pub unsafe fn steal(port: u8, pin: u8) -> Self { // Calculates the offset from the beginning of the IOPCTL register block // address to the register address representing the pin. // // See Table 297 in reference manual for how this offset is calculated. let offset = ((port as usize) << 7) + ((pin as usize) << 2); // SAFETY: This is safe assuming the caller of this function satisfies the safety requirements above. let reg = unsafe { &*Iopctl::ptr().byte_offset(offset as isize).cast() }; Self { pin_port: port * 32 + pin, reg, } } /// Returns the pin's port and pin combination. #[must_use] pub fn pin_port(&self) -> usize { self.pin_port as usize } } /// Represents a FC15 pin peripheral created at run-time from given pin number. pub struct FC15Pin { reg: &'static PioM_N, } impl FC15Pin { /// Creates an FC15 pin from raw pin number which can then be configured. /// /// This should ONLY be called when there is no other choice /// (e.g. from a type-erased GPIO pin). /// /// Otherwise, pin peripherals should be configured directly. /// /// # Safety /// /// The caller MUST ensure valid port and pin numbers are provided, /// and that multiple instances of [`AnyPin`] with the same port /// and pin combination are not being used simultaneously. /// /// Failure to uphold these requirements will result in undefined behavior. /// /// See Table 297 in reference manual for a list of valid /// pin and port number combinations. #[must_use] pub unsafe fn steal(pin: u8) -> Self { // Table 297: FC15_I2C_SCL offset = 0x400, FC15_I2C_SCL offset = 0x404 let iopctl = unsafe { crate::pac::Iopctl::steal() }; let reg = if pin == 0 { &*iopctl.fc15_i2c_scl().as_ptr().cast() } else { &*iopctl.fc15_i2c_sda().as_ptr().cast() }; Self { reg } } } // This allows AnyPin/FC15Pin to be used in HAL constructors that require types // which impl Peripheral. Used primarily by GPIO HAL to convert type-erased // GPIO pins back into an Output or Input pin specifically. embassy_hal_internal::impl_peripheral!(AnyPin); impl SealedPin for AnyPin {} embassy_hal_internal::impl_peripheral!(FC15Pin); impl SealedPin for FC15Pin {} macro_rules! impl_iopctlpin { ($pintype:ident) => { impl IopctlPin for $pintype { fn set_function(&self, function: Function) -> &Self { critical_section::with(|_| match function { Function::F0 => { self.reg.modify(|_, w| w.fsel().function_0()); } Function::F1 => { self.reg.modify(|_, w| w.fsel().function_1()); } Function::F2 => { self.reg.modify(|_, w| w.fsel().function_2()); } Function::F3 => { self.reg.modify(|_, w| w.fsel().function_3()); } Function::F4 => { self.reg.modify(|_, w| w.fsel().function_4()); } Function::F5 => { self.reg.modify(|_, w| w.fsel().function_5()); } Function::F6 => { self.reg.modify(|_, w| w.fsel().function_6()); } Function::F7 => { self.reg.modify(|_, w| w.fsel().function_7()); } Function::F8 => { self.reg.modify(|_, w| w.fsel().function_8()); } }); self } fn set_pull(&self, pull: Pull) -> &Self { critical_section::with(|_| { match pull { Pull::None => { self.reg.modify(|_, w| w.pupdena().disabled()); } Pull::Up => { self.reg.modify(|_, w| w.pupdena().enabled().pupdsel().pull_up()); } Pull::Down => { self.reg .modify(|_, w| w.pupdena().enabled().pupdsel().pull_down()); } } self }) } fn enable_input_buffer(&self) -> &Self { critical_section::with(|_| self.reg.modify(|_, w| w.ibena().enabled())); self } fn disable_input_buffer(&self) -> &Self { critical_section::with(|_| self.reg.modify(|_, w| w.ibena().disabled())); self } fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self { critical_section::with(|_| match slew_rate { SlewRate::Standard => { self.reg.modify(|_, w| w.slewrate().normal()); } SlewRate::Slow => { self.reg.modify(|_, w| w.slewrate().slow()); } }); self } fn set_drive_strength(&self, strength: DriveStrength) -> &Self { critical_section::with(|_| match strength { DriveStrength::Normal => { self.reg.modify(|_, w| w.fulldrive().normal_drive()); } DriveStrength::Full => { self.reg.modify(|_, w| w.fulldrive().full_drive()); } }); self } fn enable_analog_multiplex(&self) -> &Self { critical_section::with(|_| self.reg.modify(|_, w| w.amena().enabled())); self } fn disable_analog_multiplex(&self) -> &Self { critical_section::with(|_| self.reg.modify(|_, w| w.amena().disabled())); self } fn set_drive_mode(&self, mode: DriveMode) -> &Self { critical_section::with(|_| match mode { DriveMode::PushPull => { self.reg.modify(|_, w| w.odena().disabled()); } DriveMode::OpenDrain => { self.reg.modify(|_, w| w.odena().enabled()); } }); self } fn set_input_inverter(&self, inverter: Inverter) -> &Self { critical_section::with(|_| match inverter { Inverter::Disabled => { self.reg.modify(|_, w| w.iiena().disabled()); } Inverter::Enabled => { self.reg.modify(|_, w| w.iiena().enabled()); } }); self } fn reset(&self) -> &Self { self.reg.reset(); self } } }; } impl_iopctlpin!(AnyPin); impl_iopctlpin!(FC15Pin); macro_rules! impl_FC15pin { ($pin_periph:ident, $pin_no:expr) => { impl SealedPin for crate::peripherals::$pin_periph {} impl ToFC15Pin for crate::peripherals::$pin_periph {} impl IopctlPin for crate::peripherals::$pin_periph { #[inline] fn set_function(&self, _function: Function) -> &Self { //No function configuration for FC15 pin self } #[inline] fn set_pull(&self, pull: Pull) -> &Self { Self::to_raw($pin_no).set_pull(pull); self } #[inline] fn enable_input_buffer(&self) -> &Self { Self::to_raw($pin_no).enable_input_buffer(); self } #[inline] fn disable_input_buffer(&self) -> &Self { Self::to_raw($pin_no).disable_input_buffer(); self } #[inline] fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self { Self::to_raw($pin_no).set_slew_rate(slew_rate); self } #[inline] fn set_drive_strength(&self, strength: DriveStrength) -> &Self { Self::to_raw($pin_no).set_drive_strength(strength); self } #[inline] fn enable_analog_multiplex(&self) -> &Self { Self::to_raw($pin_no).enable_analog_multiplex(); self } #[inline] fn disable_analog_multiplex(&self) -> &Self { Self::to_raw($pin_no).disable_analog_multiplex(); self } #[inline] fn set_drive_mode(&self, mode: DriveMode) -> &Self { Self::to_raw($pin_no).set_drive_mode(mode); self } #[inline] fn set_input_inverter(&self, inverter: Inverter) -> &Self { Self::to_raw($pin_no).set_input_inverter(inverter); self } #[inline] fn reset(&self) -> &Self { Self::to_raw($pin_no).reset(); self } } }; } macro_rules! impl_pin { ($pin_periph:ident, $pin_port:expr, $pin_no:expr) => { impl SealedPin for crate::peripherals::$pin_periph {} impl ToAnyPin for crate::peripherals::$pin_periph {} impl IopctlPin for crate::peripherals::$pin_periph { #[inline] fn set_function(&self, function: Function) -> &Self { Self::to_raw($pin_port, $pin_no).set_function(function); self } #[inline] fn set_pull(&self, pull: Pull) -> &Self { Self::to_raw($pin_port, $pin_no).set_pull(pull); self } #[inline] fn enable_input_buffer(&self) -> &Self { Self::to_raw($pin_port, $pin_no).enable_input_buffer(); self } #[inline] fn disable_input_buffer(&self) -> &Self { Self::to_raw($pin_port, $pin_no).disable_input_buffer(); self } #[inline] fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self { Self::to_raw($pin_port, $pin_no).set_slew_rate(slew_rate); self } #[inline] fn set_drive_strength(&self, strength: DriveStrength) -> &Self { Self::to_raw($pin_port, $pin_no).set_drive_strength(strength); self } #[inline] fn enable_analog_multiplex(&self) -> &Self { Self::to_raw($pin_port, $pin_no).enable_analog_multiplex(); self } #[inline] fn disable_analog_multiplex(&self) -> &Self { Self::to_raw($pin_port, $pin_no).disable_analog_multiplex(); self } #[inline] fn set_drive_mode(&self, mode: DriveMode) -> &Self { Self::to_raw($pin_port, $pin_no).set_drive_mode(mode); self } #[inline] fn set_input_inverter(&self, inverter: Inverter) -> &Self { Self::to_raw($pin_port, $pin_no).set_input_inverter(inverter); self } #[inline] fn reset(&self) -> &Self { Self::to_raw($pin_port, $pin_no).reset(); self } } }; } impl_pin!(PIO0_0, 0, 0); impl_pin!(PIO0_1, 0, 1); impl_pin!(PIO0_2, 0, 2); impl_pin!(PIO0_3, 0, 3); impl_pin!(PIO0_4, 0, 4); impl_pin!(PIO0_5, 0, 5); impl_pin!(PIO0_6, 0, 6); impl_pin!(PIO0_7, 0, 7); impl_pin!(PIO0_8, 0, 8); impl_pin!(PIO0_9, 0, 9); impl_pin!(PIO0_10, 0, 10); impl_pin!(PIO0_11, 0, 11); impl_pin!(PIO0_12, 0, 12); impl_pin!(PIO0_13, 0, 13); impl_pin!(PIO0_14, 0, 14); impl_pin!(PIO0_15, 0, 15); impl_pin!(PIO0_16, 0, 16); impl_pin!(PIO0_17, 0, 17); impl_pin!(PIO0_18, 0, 18); impl_pin!(PIO0_19, 0, 19); impl_pin!(PIO0_20, 0, 20); impl_pin!(PIO0_21, 0, 21); impl_pin!(PIO0_22, 0, 22); impl_pin!(PIO0_23, 0, 23); impl_pin!(PIO0_24, 0, 24); impl_pin!(PIO0_25, 0, 25); impl_pin!(PIO0_26, 0, 26); impl_pin!(PIO0_27, 0, 27); impl_pin!(PIO0_28, 0, 28); impl_pin!(PIO0_29, 0, 29); impl_pin!(PIO0_30, 0, 30); impl_pin!(PIO0_31, 0, 31); impl_pin!(PIO1_0, 1, 0); impl_pin!(PIO1_1, 1, 1); impl_pin!(PIO1_2, 1, 2); impl_pin!(PIO1_3, 1, 3); impl_pin!(PIO1_4, 1, 4); impl_pin!(PIO1_5, 1, 5); impl_pin!(PIO1_6, 1, 6); impl_pin!(PIO1_7, 1, 7); impl_pin!(PIO1_8, 1, 8); impl_pin!(PIO1_9, 1, 9); impl_pin!(PIO1_10, 1, 10); impl_pin!(PIO1_11, 1, 11); impl_pin!(PIO1_12, 1, 12); impl_pin!(PIO1_13, 1, 13); impl_pin!(PIO1_14, 1, 14); impl_pin!(PIO1_15, 1, 15); impl_pin!(PIO1_16, 1, 16); impl_pin!(PIO1_17, 1, 17); impl_pin!(PIO1_18, 1, 18); impl_pin!(PIO1_19, 1, 19); impl_pin!(PIO1_20, 1, 20); impl_pin!(PIO1_21, 1, 21); impl_pin!(PIO1_22, 1, 22); impl_pin!(PIO1_23, 1, 23); impl_pin!(PIO1_24, 1, 24); impl_pin!(PIO1_25, 1, 25); impl_pin!(PIO1_26, 1, 26); impl_pin!(PIO1_27, 1, 27); impl_pin!(PIO1_28, 1, 28); impl_pin!(PIO1_29, 1, 29); impl_pin!(PIO1_30, 1, 30); impl_pin!(PIO1_31, 1, 31); impl_pin!(PIO2_0, 2, 0); impl_pin!(PIO2_1, 2, 1); impl_pin!(PIO2_2, 2, 2); impl_pin!(PIO2_3, 2, 3); impl_pin!(PIO2_4, 2, 4); impl_pin!(PIO2_5, 2, 5); impl_pin!(PIO2_6, 2, 6); impl_pin!(PIO2_7, 2, 7); impl_pin!(PIO2_8, 2, 8); impl_pin!(PIO2_9, 2, 9); impl_pin!(PIO2_10, 2, 10); impl_pin!(PIO2_11, 2, 11); impl_pin!(PIO2_12, 2, 12); impl_pin!(PIO2_13, 2, 13); impl_pin!(PIO2_14, 2, 14); impl_pin!(PIO2_15, 2, 15); impl_pin!(PIO2_16, 2, 16); impl_pin!(PIO2_17, 2, 17); impl_pin!(PIO2_18, 2, 18); impl_pin!(PIO2_19, 2, 19); impl_pin!(PIO2_20, 2, 20); impl_pin!(PIO2_21, 2, 21); impl_pin!(PIO2_22, 2, 22); impl_pin!(PIO2_23, 2, 23); impl_pin!(PIO2_24, 2, 24); // Note: These have have reset values of 0x41 to support SWD by default impl_pin!(PIO2_25, 2, 25); impl_pin!(PIO2_26, 2, 26); impl_pin!(PIO2_27, 2, 27); impl_pin!(PIO2_28, 2, 28); impl_pin!(PIO2_29, 2, 29); impl_pin!(PIO2_30, 2, 30); impl_pin!(PIO2_31, 2, 31); impl_pin!(PIO3_0, 3, 0); impl_pin!(PIO3_1, 3, 1); impl_pin!(PIO3_2, 3, 2); impl_pin!(PIO3_3, 3, 3); impl_pin!(PIO3_4, 3, 4); impl_pin!(PIO3_5, 3, 5); impl_pin!(PIO3_6, 3, 6); impl_pin!(PIO3_7, 3, 7); impl_pin!(PIO3_8, 3, 8); impl_pin!(PIO3_9, 3, 9); impl_pin!(PIO3_10, 3, 10); impl_pin!(PIO3_11, 3, 11); impl_pin!(PIO3_12, 3, 12); impl_pin!(PIO3_13, 3, 13); impl_pin!(PIO3_14, 3, 14); impl_pin!(PIO3_15, 3, 15); impl_pin!(PIO3_16, 3, 16); impl_pin!(PIO3_17, 3, 17); impl_pin!(PIO3_18, 3, 18); impl_pin!(PIO3_19, 3, 19); impl_pin!(PIO3_20, 3, 20); impl_pin!(PIO3_21, 3, 21); impl_pin!(PIO3_22, 3, 22); impl_pin!(PIO3_23, 3, 23); impl_pin!(PIO3_24, 3, 24); impl_pin!(PIO3_25, 3, 25); impl_pin!(PIO3_26, 3, 26); impl_pin!(PIO3_27, 3, 27); impl_pin!(PIO3_28, 3, 28); impl_pin!(PIO3_29, 3, 29); impl_pin!(PIO3_30, 3, 30); impl_pin!(PIO3_31, 3, 31); impl_pin!(PIO4_0, 4, 0); impl_pin!(PIO4_1, 4, 1); impl_pin!(PIO4_2, 4, 2); impl_pin!(PIO4_3, 4, 3); impl_pin!(PIO4_4, 4, 4); impl_pin!(PIO4_5, 4, 5); impl_pin!(PIO4_6, 4, 6); impl_pin!(PIO4_7, 4, 7); impl_pin!(PIO4_8, 4, 8); impl_pin!(PIO4_9, 4, 9); impl_pin!(PIO4_10, 4, 10); impl_pin!(PIO7_24, 7, 24); impl_pin!(PIO7_25, 7, 25); impl_pin!(PIO7_26, 7, 26); impl_pin!(PIO7_27, 7, 27); impl_pin!(PIO7_28, 7, 28); impl_pin!(PIO7_29, 7, 29); impl_pin!(PIO7_30, 7, 30); impl_pin!(PIO7_31, 7, 31); // FC15 pins impl_FC15pin!(PIOFC15_SCL, 0); impl_FC15pin!(PIOFC15_SDA, 1); ================================================ FILE: embassy-imxrt/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![allow(unsafe_op_in_unsafe_fn)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] //! ## Feature flags #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] #[cfg(not(any(feature = "mimxrt633s", feature = "mimxrt685s",)))] compile_error!( "No chip feature activated. You must activate exactly one of the following features: mimxrt633s, mimxrt685s, " ); // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; pub mod clocks; pub mod crc; pub mod dma; pub mod flexcomm; pub mod gpio; pub mod iopctl; pub mod rng; #[cfg(feature = "_time-driver")] pub mod time_driver; // This mod MUST go last, so that it sees all the `impl_foo!' macros #[cfg_attr(feature = "mimxrt633s", path = "chips/mimxrt633s.rs")] #[cfg_attr(feature = "mimxrt685s", path = "chips/mimxrt685s.rs")] mod chip; // Reexports pub use chip::interrupts::*; #[cfg(feature = "unstable-pac")] pub use chip::pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use chip::pac; pub use chip::{Peripherals, peripherals}; pub use embassy_hal_internal::{Peri, PeripheralType}; #[cfg(feature = "rt")] pub use crate::pac::NVIC_PRIO_BITS; /// 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_imxrt::{bind_interrupts, flexspi, peripherals}; /// /// bind_interrupts!( /// /// Binds the FLEXSPI interrupt. /// struct Irqs { /// FLEXSPI_IRQ => flexspi::InterruptHandler; /// } /// ); /// ``` /// // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { ($(#[$attr:meta])* $vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { #[derive(Copy, Clone)] $(#[$attr])* $vis struct $name; $( #[allow(non_snake_case)] #[unsafe(no_mangle)] unsafe extern "C" fn $irq() { unsafe { $( <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); )* } } $( unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* )* }; } /// HAL configuration for iMX RT600. pub mod config { use crate::clocks::ClockConfig; /// HAL configuration passed when initializing. #[non_exhaustive] pub struct Config { /// Clock configuration. pub clocks: ClockConfig, /// RTC Time driver interrupt priority. #[cfg(feature = "_time-driver")] pub time_interrupt_priority: crate::interrupt::Priority, } impl Default for Config { fn default() -> Self { Self { clocks: ClockConfig::crystal(), #[cfg(feature = "_time-driver")] time_interrupt_priority: crate::interrupt::Priority::P0, } } } impl Config { /// Create a new configuration with the provided clock config. pub fn new(clocks: ClockConfig) -> Self { Self { clocks, #[cfg(feature = "_time-driver")] time_interrupt_priority: crate::interrupt::Priority::P0, } } } } /// Initialize the `embassy-imxrt` 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(); #[cfg(feature = "_time-driver")] time_driver::init(config.time_interrupt_priority); unsafe { if let Err(e) = clocks::init(config.clocks) { error!("unable to initialize Clocks for reason: {:?}", e); // Panic here? } dma::init(); } gpio::init(); peripherals } pub(crate) mod sealed { pub trait Sealed {} } #[cfg(feature = "rt")] struct BitIter(u32); #[cfg(feature = "rt")] 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) } } } } ================================================ FILE: embassy-imxrt/src/rng.rs ================================================ //! True Random Number Generator (TRNG) use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; use embassy_futures::block_on; use embassy_sync::waitqueue::AtomicWaker; use crate::clocks::{SysconPeripheral, enable_and_reset}; use crate::interrupt::typelevel::Interrupt; use crate::{Peri, PeripheralType, interrupt, peripherals}; static RNG_WAKER: AtomicWaker = AtomicWaker::new(); /// RNG ;error #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// Seed error. SeedError, /// HW Error. HwError, /// Frequency Count Fail FreqCountFail, } /// RNG interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let regs = T::info().regs; let int_status = regs.int_status().read(); if int_status.ent_val().bit_is_set() || int_status.hw_err().bit_is_set() || int_status.frq_ct_fail().bit_is_set() { regs.int_ctrl().modify(|_, w| { w.ent_val() .ent_val_0() .hw_err() .hw_err_0() .frq_ct_fail() .frq_ct_fail_0() }); RNG_WAKER.wake(); } } } /// RNG driver. pub struct Rng<'d> { info: Info, _lifetime: PhantomData<&'d ()>, } impl<'d> Rng<'d> { /// Create a new RNG driver. pub fn new( _inner: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { enable_and_reset::(); let mut random = Self { info: T::info(), _lifetime: PhantomData, }; random.init(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; random } /// Reset the RNG. pub fn reset(&mut self) { self.info.regs.mctl().write(|w| w.rst_def().set_bit().prgm().set_bit()); } /// Fill the given slice with random values. pub async fn async_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { // We have a total of 16 words (512 bits) of entropy at our // disposal. The idea here is to read all bits and copy the // necessary bytes to the slice. for chunk in dest.chunks_mut(64) { self.async_fill_chunk(chunk).await?; } Ok(()) } async fn async_fill_chunk(&mut self, chunk: &mut [u8]) -> Result<(), Error> { // wait for interrupt let res = poll_fn(|cx| { // Check if already ready. // TODO: Is this necessary? Could we just check once after // the waker has been registered? if self.info.regs.int_status().read().ent_val().bit_is_set() { return Poll::Ready(Ok(())); } RNG_WAKER.register(cx.waker()); self.unmask_interrupts(); let mctl = self.info.regs.mctl().read(); // Check again if interrupt fired if mctl.ent_val().bit_is_set() { Poll::Ready(Ok(())) } else if mctl.err().bit_is_set() { Poll::Ready(Err(Error::HwError)) } else if mctl.fct_fail().bit_is_set() { Poll::Ready(Err(Error::FreqCountFail)) } else { Poll::Pending } }) .await; let bits = self.info.regs.mctl().read(); if bits.ent_val().bit_is_set() { let mut entropy = [0; 16]; for (i, item) in entropy.iter_mut().enumerate() { *item = self.info.regs.ent(i).read().bits(); } // Read MCTL after reading ENT15 let _ = self.info.regs.mctl().read(); if entropy.iter().any(|e| *e == 0) { return Err(Error::SeedError); } // SAFETY: entropy is the same for input and output types in // native endianness. let entropy: [u8; 64] = unsafe { core::mem::transmute(entropy) }; // write bytes to chunk chunk.copy_from_slice(&entropy[..chunk.len()]); } res } fn mask_interrupts(&mut self) { self.info.regs.int_mask().write(|w| { w.ent_val() .ent_val_0() .hw_err() .hw_err_0() .frq_ct_fail() .frq_ct_fail_0() }); } fn unmask_interrupts(&mut self) { self.info.regs.int_mask().modify(|_, w| { w.ent_val() .ent_val_1() .hw_err() .hw_err_1() .frq_ct_fail() .frq_ct_fail_1() }); } fn enable_interrupts(&mut self) { self.info.regs.int_ctrl().write(|w| { w.ent_val() .ent_val_1() .hw_err() .hw_err_1() .frq_ct_fail() .frq_ct_fail_1() }); } fn init(&mut self) { self.mask_interrupts(); // Switch TRNG to programming mode self.info.regs.mctl().modify(|_, w| w.prgm().set_bit()); self.enable_interrupts(); // Switch TRNG to Run Mode self.info .regs .mctl() .modify(|_, w| w.trng_acc().set_bit().prgm().clear_bit()); } /// Generate a random u32 pub fn blocking_next_u32(&mut self) -> u32 { let mut bytes = [0u8; 4]; block_on(self.async_fill_bytes(&mut bytes)).unwrap(); u32::from_ne_bytes(bytes) } /// Generate a random u64 pub fn blocking_next_u64(&mut self) -> u64 { let mut bytes = [0u8; 8]; block_on(self.async_fill_bytes(&mut bytes)).unwrap(); u64::from_ne_bytes(bytes) } /// Fill a slice with random bytes. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { block_on(self.async_fill_bytes(dest)).unwrap(); } } impl<'d> rand_core_06::RngCore for Rng<'d> { 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<(), rand_core_06::Error> { self.blocking_fill_bytes(dest); Ok(()) } } impl<'d> rand_core_06::CryptoRng for Rng<'d> {} impl<'d> rand_core_09::RngCore for Rng<'d> { 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); } } impl<'d> rand_core_09::CryptoRng for Rng<'d> {} struct Info { regs: crate::pac::Trng, } trait SealedInstance { fn info() -> Info; } /// RNG instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + SysconPeripheral + 'static + Send { /// Interrupt for this RNG instance. type Interrupt: interrupt::typelevel::Interrupt; } impl Instance for peripherals::RNG { type Interrupt = crate::interrupt::typelevel::RNG; } impl SealedInstance for peripherals::RNG { fn info() -> Info { // SAFETY: safe from single executor Info { regs: unsafe { crate::pac::Trng::steal() }, } } } ================================================ FILE: embassy-imxrt/src/time_driver.rs ================================================ //! Time Driver. use core::cell::{Cell, RefCell}; #[cfg(feature = "time-driver-rtc")] use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; use critical_section::CriticalSection; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_time_driver::Driver; use embassy_time_queue_utils::Queue; #[cfg(feature = "time-driver-os-timer")] use crate::clocks::enable; use crate::interrupt::InterruptExt; use crate::{interrupt, pac}; struct AlarmState { timestamp: Cell, } unsafe impl Send for AlarmState {} impl AlarmState { const fn new() -> Self { Self { timestamp: Cell::new(u64::MAX), } } } #[cfg(feature = "time-driver-rtc")] fn rtc() -> &'static pac::rtc::RegisterBlock { unsafe { &*pac::Rtc::ptr() } } /// Calculate the timestamp from the period count and the tick count. /// /// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches /// the expected range for the `period` parity, we're done. If it doesn't, this means that /// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value /// corresponds to the next period. /// /// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels, /// so using a 32 bit GPREG0-2 as counter, compare, and int_en /// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection #[cfg(feature = "time-driver-rtc")] fn calc_now(period: u32, counter: u32) -> u64 { ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) } #[cfg(feature = "time-driver-rtc")] embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { period: AtomicU32::new(0), alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), queue: Mutex::new(RefCell::new(Queue::new())), }); #[cfg(feature = "time-driver-rtc")] struct Rtc { /// Number of 2^31 periods elapsed since boot. period: AtomicU32, /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. alarms: Mutex, queue: Mutex>, } #[cfg(feature = "time-driver-rtc")] impl Rtc { /// Access the GPREG0 register to use it as a 31-bit counter. #[inline] fn counter_reg(&self) -> &pac::rtc::Gpreg { rtc().gpreg(0) } /// Access the GPREG1 register to use it as a compare register for triggering alarms. #[inline] fn compare_reg(&self) -> &pac::rtc::Gpreg { rtc().gpreg(1) } /// Access the GPREG2 register to use it to enable or disable interrupts (int_en). #[inline] fn int_en_reg(&self) -> &pac::rtc::Gpreg { rtc().gpreg(2) } fn init(&'static self, irq_prio: crate::interrupt::Priority) { let r = rtc(); // enable RTC int (1kHz since subsecond doesn't generate an int) r.ctrl().modify(|_r, w| w.rtc1khz_en().set_bit()); // TODO: low power support. line above is leaving out write to .wakedpd_en().set_bit()) // which enables wake from deep power down // safety: Writing to the gregs is always considered unsafe, gpreg1 is used // as a compare register for triggering an alarm so to avoid unnecessary triggers // after initialization, this is set to 0x:FFFF_FFFF self.compare_reg().write(|w| unsafe { w.gpdata().bits(u32::MAX) }); // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. // The following loads 10 into the count-down timer. r.wake().write(|w| unsafe { w.bits(0xA) }); interrupt::RTC.set_priority(irq_prio); unsafe { interrupt::RTC.enable() }; } #[cfg(feature = "rt")] fn on_interrupt(&self) { let r = rtc(); // This interrupt fires every 10 ticks of the 1kHz RTC high res clk and adds // 10 to the 31 bit counter gpreg0. The 32nd bit is used for parity detection // This is done to avoid needing to calculate # of ticks spent on interrupt // handlers to recalibrate the clock between interrupts // // TODO: this is admittedly not great for power that we're generating this // many interrupts, will probably get updated in future iterations. if r.ctrl().read().wake1khz().bit_is_set() { r.ctrl().modify(|_r, w| w.wake1khz().set_bit()); // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. // The following reloads 10 into the count-down timer after it triggers an int. // The countdown begins anew after the write so time can continue to be measured. r.wake().write(|w| unsafe { w.bits(0xA) }); if (self.counter_reg().read().bits() + 0xA) > 0x8000_0000 { // if we're going to "overflow", increase the period self.next_period(); let rollover_diff = 0x8000_0000 - (self.counter_reg().read().bits() + 0xA); // safety: writing to gpregs is always considered unsafe. In order to // not "lose" time when incrementing the period, gpreg0, the extended // counter, is restarted at the # of ticks it would overflow by self.counter_reg().write(|w| unsafe { w.bits(rollover_diff) }); } else { self.counter_reg().modify(|r, w| unsafe { w.bits(r.bits() + 0xA) }); } } critical_section::with(|cs| { // gpreg2 as an "int_en" set by next_period(). This is // 1 when the timestamp for the alarm deadline expires // before the counter register overflows again. if self.int_en_reg().read().gpdata().bits() == 1 { // gpreg0 is our extended counter register, check if // our counter is larger than the compare value if self.counter_reg().read().bits() > self.compare_reg().read().bits() { self.trigger_alarm(cs); } } }) } #[cfg(feature = "rt")] fn next_period(&self) { critical_section::with(|cs| { let period = self .period .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |p| Some(p + 1)) .unwrap_or_else(|p| { trace!("Unable to increment period. Time is now inaccurate"); // TODO: additional error handling beyond logging p }); let t = (period as u64) << 31; let alarm = &self.alarms.borrow(cs); let at = alarm.timestamp.get(); if at < t + 0xc000_0000 { // safety: writing to gpregs is always unsafe, gpreg2 is an alarm // enable. If the alarm must trigger within the next period, then // just enable it. `set_alarm` has already set the correct CC val. self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); } }) } #[must_use] fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { let alarm = self.alarms.borrow(cs); alarm.timestamp.set(timestamp); let t = self.now(); if timestamp <= t { // safety: Writing to the gpregs is always unsafe, gpreg2 is // always just used as the alarm enable for the timer driver. // If alarm timestamp has passed the alarm will not fire. // Disarm the alarm and return `false` to indicate that. self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); alarm.timestamp.set(u64::MAX); return false; } // If it hasn't triggered yet, setup it by writing to the compare field // An alarm can be delayed, but this is allowed by the Alarm trait contract. // What's not allowed is triggering alarms *before* their scheduled time, let safe_timestamp = timestamp.max(t + 10); //t+3 was done for nrf chip, choosing 10 // safety: writing to the gregs is always unsafe. When a new alarm is set, // the compare register, gpreg1, is set to the last 31 bits of the timestamp // as the 32nd and final bit is used for the parity check in `next_period` // `period` will be used for the upper bits in a timestamp comparison. self.compare_reg() .modify(|_r, w| unsafe { w.bits(safe_timestamp as u32 & 0x7FFF_FFFF) }); // The following checks that the difference in timestamp is less than the overflow period let diff = timestamp - t; if diff < 0xc000_0000 { // this is 0b11 << (30). NRF chip used 23 bit periods and checked against 0b11<<22 // safety: writing to the gpregs is always unsafe. If the alarm // must trigger within the next period, set the "int enable" self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); } else { // safety: writing to the gpregs is always unsafe. If alarm must trigger // some time after the current period, too far in the future, don't setup // the alarm enable, gpreg2, yet. It will be setup later by `next_period`. self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); } true } #[cfg(feature = "rt")] fn trigger_alarm(&self, cs: CriticalSection) { let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); while !self.set_alarm(cs, next) { next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); } } } #[cfg(feature = "time-driver-rtc")] impl Driver for Rtc { fn now(&self) -> u64 { // `period` MUST be read before `counter`, see comment at the top for details. let period = self.period.load(Ordering::Acquire); compiler_fence(Ordering::Acquire); let counter = self.counter_reg().read().bits(); 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(all(feature = "rt", feature = "time-driver-rtc"))] #[allow(non_snake_case)] #[interrupt] fn RTC() { DRIVER.on_interrupt() } #[cfg(feature = "time-driver-os-timer")] fn os() -> &'static pac::ostimer0::RegisterBlock { unsafe { &*pac::Ostimer0::ptr() } } /// Convert gray to decimal /// /// Os Event provides a 64-bit timestamp gray-encoded. All we have to /// do here is read both 32-bit halves of the register and convert /// from gray to regular binary. #[cfg(feature = "time-driver-os-timer")] fn gray_to_dec(gray: u64) -> u64 { let mut dec = gray; dec ^= dec >> 1; dec ^= dec >> 2; dec ^= dec >> 4; dec ^= dec >> 8; dec ^= dec >> 16; dec ^= dec >> 32; dec } /// Convert decimal to gray /// /// Before writing match value to the target register, we must convert /// it back into gray code. #[cfg(feature = "time-driver-os-timer")] fn dec_to_gray(dec: u64) -> u64 { let gray = dec; gray ^ (gray >> 1) } #[cfg(feature = "time-driver-os-timer")] embassy_time_driver::time_driver_impl!(static DRIVER: OsTimer = OsTimer { alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), queue: Mutex::new(RefCell::new(Queue::new())), }); #[cfg(feature = "time-driver-os-timer")] struct OsTimer { /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. alarms: Mutex, queue: Mutex>, } #[cfg(feature = "time-driver-os-timer")] impl OsTimer { fn init(&'static self, irq_prio: crate::interrupt::Priority) { // init alarms critical_section::with(|cs| { let alarm = DRIVER.alarms.borrow(cs); alarm.timestamp.set(u64::MAX); }); // Enable clocks. Documentation advises AGAINST resetting this // peripheral. enable::(); interrupt::OS_EVENT.disable(); // Make sure interrupt is masked os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); // Default to the end of time os().match_l().write(|w| unsafe { w.bits(0xffff_ffff) }); os().match_h().write(|w| unsafe { w.bits(0xffff_ffff) }); interrupt::OS_EVENT.unpend(); interrupt::OS_EVENT.set_priority(irq_prio); unsafe { interrupt::OS_EVENT.enable() }; } fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { let alarm = self.alarms.borrow(cs); alarm.timestamp.set(timestamp); // Wait until we're allowed to write to MATCH_L/MATCH_H // registers while os().osevent_ctrl().read().match_wr_rdy().bit_is_set() {} let t = self.now(); if timestamp <= t { os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); alarm.timestamp.set(u64::MAX); return false; } let gray_timestamp = dec_to_gray(timestamp); os().match_l() .write(|w| unsafe { w.bits(gray_timestamp as u32 & 0xffff_ffff) }); os().match_h() .write(|w| unsafe { w.bits((gray_timestamp >> 32) as u32) }); os().osevent_ctrl().modify(|_, w| w.ostimer_intena().set_bit()); true } #[cfg(feature = "rt")] fn trigger_alarm(&self, cs: CriticalSection) { let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); while !self.set_alarm(cs, next) { next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); } } #[cfg(feature = "rt")] fn on_interrupt(&self) { critical_section::with(|cs| { if os().osevent_ctrl().read().ostimer_intrflag().bit_is_set() { os().osevent_ctrl() .modify(|_, w| w.ostimer_intena().clear_bit().ostimer_intrflag().set_bit()); self.trigger_alarm(cs); } }); } } #[cfg(feature = "time-driver-os-timer")] impl Driver for OsTimer { fn now(&self) -> u64 { let mut t = os().evtimerh().read().bits() as u64; t <<= 32; t |= os().evtimerl().read().bits() as u64; gray_to_dec(t) } fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { critical_section::with(|cs| { let mut queue = self.queue.borrow(cs).borrow_mut(); if queue.schedule_wake(at, waker) { let mut next = queue.next_expiration(self.now()); while !self.set_alarm(cs, next) { next = queue.next_expiration(self.now()); } } }) } } #[cfg(all(feature = "rt", feature = "time-driver-os-timer"))] #[allow(non_snake_case)] #[interrupt] fn OS_EVENT() { DRIVER.on_interrupt() } pub(crate) fn init(irq_prio: crate::interrupt::Priority) { DRIVER.init(irq_prio) } ================================================ FILE: embassy-mcxa/.gitignore ================================================ # Rust /target/ # IDE .vscode/ .idea/ # OS .DS_Store Thumbs.db # Embedded *.bin *.hex *.elf *.map # Debug *.log ================================================ FILE: embassy-mcxa/Cargo.toml ================================================ [package] name = "embassy-mcxa" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for NXP MCXA series of MCUs" repository = "https://github.com/embassy-rs/embassy" keywords = ["embedded", "hal", "nxp", "mcxa", "embassy"] categories = ["embedded", "hardware-support", "no-std"] documentation = "https://docs.embassy.dev/embassy-mcxa" publish = false [package.metadata.embassy] build = [ {target = "thumbv8m.main-none-eabihf", features = ["defmt", "unstable-pac", "mcxa2xx"]}, {target = "thumbv8m.main-none-eabihf", features = ["defmt", "unstable-pac", "mcxa5xx"]}, ] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-mcxa-v$VERSION/embassy-mcxa/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-mcxa/src/" features = ["defmt", "unstable-pac"] flavors = [ { name = "mcx-a256", target = "thumbv8m.main-none-eabihf", features = ["mcxa2xx"] }, { name = "mcx-a577", target = "thumbv8m.main-none-eabihf", features = ["mcxa5xx"] }, ] [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core", "inline-asm"] } # If you would like "device" to be an optional feature, please open an issue. cortex-m-rt = { version = "0.7", features = ["device"] } critical-section = "1.2.0" defmt = { version = "1.0", optional = true } embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-hal-internal = { version = "0.5.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-nb = { version = "1.0" } embedded-io = "0.7" embedded-io-async = { version = "0.7.0" } heapless = "0.9" maitake-sync = { version = "0.3.0", default-features = false, features = ["critical-section", "no-cache-pad"] } nxp-pac = { git = "https://github.com/embassy-rs/nxp-pac.git", rev = "c2f056f666c766563c7a22cb48d5f7a199714d7f", features = ["rt"] } nb = "1.1.0" paste = "1.0.15" # Custom executor support embassy-executor = { version = "0.10.0", path = "../embassy-executor" } # `time` dependencies embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", features = ["tick-hz-1_000_000"] } embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-utils" } rand-core-06 = { package = "rand_core", version = "0.6" } rand-core-09 = { package = "rand_core", version = "0.9" } embedded-storage = "0.3.1" bbqueue = { version = "0.7.0", features = ["disable-cache-padding"] } grounded = { version = "0.2.1", features = ["cas", "critical-section"] } [features] default = ["rt", "swd-swo-as-gpio"] # Base defmt feature enables core + panic handler # Use with one logger feature: defmt-rtt (preferred) or defmt-uart (fallback) defmt = ["dep:defmt", "nxp-pac/defmt"] unstable-pac = [] # Allows for using SOSC pins as normal GPIOs. # # DISABLING this allows for use of SOSC, but reserves the GPIO pins required. # ENABLING this means SOSC cannot be set/configured, but makes the GPIO pins available sosc-as-gpio = [] # Allows for using the ROSC (32k external clock) pins as normal GPIOs. # # DISABLING this allows for use of the 32k clock, but reserves the GPIO pins required. # ENABLING this means the 32k clock cannot be set/configured, but makes the GPIO pins available # # NOTE: Only applicable to MCXA5xx. rosc-32k-as-gpio = [] # Allows for using SWDIO and SWCLK pins as normal GPIOs. # # WARNING: This WILL cause problems if you plan to use defmt over RTT, # or otherwise debug the system while running. # # If this feature is enabled, the relevant pin(s) will be set to floating inputs # in the HAL init function. # # This does NOT include the SWO pin. swd-as-gpio = [] # Allows for using the SWD SWO pins as a normal GPIO. This is not used for # basic probe-rs/rtt usage, but is sometimes used with ITM. # # If this feature is enabled, the relevant pin(s) will be set to floating inputs # in the HAL init function. swd-swo-as-gpio = [] # Allows for using the extra JTAG pins (TDI, ISPMODE_N) as normal GPIOs. You # must also select `swd-as-gpio`, `swd-swo-as-gpio`, and MAYBE `dangerous-reset-as-gpio` # to recover all JTAG pins. These are called "extras" because they aren't also used # for normal SWD/SWO. jtag-extras-as-gpio = [] # Allows for using the reset pin as a normal GPIO. # # This is NOT recommended, and we have not currently tested whether it remains # possible to reset the device and reflash using probe-rs after enabling this # feature. Use at your own risk! dangerous-reset-as-gpio = [] # dummy feature to silence embassy-hal-internal lint # # This feature makes no change to embassy-mcxa's operation. rt = [] # Enable perf counters perf = [] # Enable the custom executor platform implementation, which allows for low-power modes. # # If this feature is enabled, users should NOT enable `embassy-executor/platform-cortex-m` or any other executor platform. executor-platform = [] # This feature is not done yet, don't use it! unstable-osc32k = [] # # Chip family selection # mcxa2xx = ["nxp-pac/mcxa256"] mcxa5xx = ["nxp-pac/mcxa577"] [dev-dependencies] embassy-executor = { version = "0.10.0", path = "../embassy-executor", features = ["platform-cortex-m", "executor-interrupt", "executor-thread"] } embedded-storage = "0.3.1" panic-halt = "1.0.0" static_cell = "2.1.1" ================================================ FILE: embassy-mcxa/DEVGUIDE.md ================================================ # Embassy MCXA Developer's Guide This document is intended to assist developers of the `embassy-mcxa` crate. As of 2026-01-29, there is currently no "how to write/maintain a HAL" guide for `embassy`, so we intend to write up and explain why the embassy-mcxa crate was implemented the way it was, and to serve as a reference for people incrementally building out more features in the future. We also hope to "upstream" these docs when possible, to assist with better consistency among embassy HALs in the future. This document will be written incrementally. If you see something missing: please do one of the following: * Open an issue in the embassy github * Ask in the embassy matrix chat * Open a PR to add the documentation you think is missing ## FRDM Usage Tips ### Recovering from a too-sleepy firmware If you have an example that is configured to the DeepSleep state, it will sever the debugger connection once it enters deep sleep. This can mean it will be hard to re-flash since the debugging core is disabled. To recover from this state, you can use the ISP mode, which triggers the ROM bootloader: 1. Hold the "ISP" button down 2. Tap and release the "RESET" button 3. Release the "ISP" button 4. Try to flash with probe-rs, the first time will likely fail (I don't know why yet, it probably makes the bootloader upset) 5. Try to flash again, the second time will likely work. You probably want to recover the device by flashing a simple example like the `blinky` example which doesn't attempt to go to deep sleep. ## The `Cargo.toml` file This section describes the notable components of the `Cargo.toml` package manifest. ### `package.metadata` As an embassy crate, we have a couple of embassy-specific metadata sections. * `package.metadata.embassy` * This section is used for determining how to build the crate for embassy's CI process. * `package.metadata.embassy_docs` * This section is used for determining how to generate embassy's API docs. * See . * These docs are rebuilt after each PR is merged, with a short debouncing period. ### Features We have a couple of features/kinds of features exposed as part of the crate. For general features, see the `Cargo.toml` docs for what features are activated by default, and what these features do. Notable features/groupings of features are discussed below. #### `...-as-gpio` features Some pins can operate EITHER for GPIO/peripheral use, OR for some kind of dedicated feature, such as SWD/JTAG debugging, external oscillator, etc. Since it is difficult to expose this conditionally in the `Peripherals` struct returned by `hal::init()`, we make this a compile-time feature decision. This is generally reasonable, because when pins are dedicated to a use (or not), this requires board-level electrical wiring, which is not typically reconfigured at runtime. For pins covered by `...-as-gpio` features, they are typically in their dedicated feature mode at boot. When an `...-as-gpio` feature is active, the relevant pins will be moved back to the "disabled" state at boot, rather than remaining in their default dedicated feature state. For example, the `swd-swo-as-gpio` feature is on by default. When this feature is NOT enabled, the pin is used as SWO by default. On the FRDM development board, this causes issues, as this pin is NOT wired up to SWO, and is instead wired up to the I2C/I3C circuit, preventing normal operation. ## The top level of the crate - `lib.rs` The `lib.rs` is the top level API of the `embassy-mcxa` crate. ### `embassy_hal_internal::peripherals!` The `embassy_hal_internal::peripherals!` macro is used to create the list of peripherals available to users of the HAL after calling `hal::init()`. Each item generates a `Peri<'static, T>`, which is a zero-sized type "token", which is used to prove exclusive access to a peripheral. These are often referred to as "singletons", as these tokens can only (safely) be created once. For more information on how these tokens are used, see the "Peripheral Drivers" section below. In this list, we include: * All hardware peripherals. * Any "synthetic" peripherals that we also want to exist as a singleton, even if they are not a "real" hardware peripheral. The generated `Peripherals` struct always creates all items, which means it's not generally possible for functions like `hal::init()` to say "depending on config, we MIGHT not give you back some pins/peripherals". For this reason, we make any of these conditionally-returned tokens a crate feature. See the `Cargo.toml` section above for more details. ### `embassy_hal_internal::interrupt_mod!` The `embassy_hal_internal::interrupt_mod!` macro is used to generate a number of helper functions, types, and marker traits for each hardware interrupt signal on the chip. All interrupts available for a chip should be listed in this macro. ### The `init` function This function is also referred to as `hal::init()` in these docs. This function is typically one of the first functions called by the user. It takes all configuration values relevant for the lifetime of the firmware, including: * The priority level for any "automagically handled" peripheral interrupts, including: * GPIOs * RTC * OsTimer (used for `embassy-time-driver` impl) * DMA * The Clock and Power configurations for Active (running and WFE sleep) and Deep Sleep modes This function then performs important "boot up" work, including: * Enabling system level clocks and power based on the user configuration * Enabling and configuring "automagically handled" peripherals (those listed above) * Enabling and configuring the priority of interrupts for "automagically handled" peripherals Finally, when setup is complete, The `init` function returns the `Peripherals` struct, created by the `embassy_hal_internal::peripherals!` macro, containing one `Peri<'static, T>` token for each peripheral. ## Non-Peripheral Components Some modules of the HAL do not map 1:1 with the memory mapped peripherals of the system. These components are discussed here. ### Clocking and Power subsystem The `clocks` module is responsible for setting up the system clock and power configuration of the device. This functionality spans across a few peripherals (`SCG`, `SYSCON`, `VBAT`, `MRCC`, etc.). See the doc comments of `src/clocks/mod.rs` for more details regarding the architectural choices of this module. ## Peripheral Drivers The majority of `embassy-mcxa` handles high-level drivers for hardware peripherals of the MCXA. These sections discuss "best practices" or "notable oddities" for these hardware drivers ### General Guidelines This section regards patterns that are used for all or most peripheral drivers. #### Type Erasure and Constructors In order to prevent "monomorphization bloat", as well as "cognitive overload" for HAL users, each peripheral driver should strive to MINIMIZE the number of lifetimes and generics present on the driver. For example, for an I2c peripheral with two GPIO pins, we DO NOT want: ```rust struct<'p, 'c, 'd, P, SCL, SDA, MODE> I2c { /* ... */ } type Example = I2c< 'periph, // lifetimes 'scl, // lifetimes 'sda, // lifetimes Peri<'periph, I2C0>, // peripheral instance generic Peri<'scl, P0_2>, // gpio pin instance generic Peri<'sda, P0_3>, // gpio pin instance generic Async, // operational mode >; ``` Instead, we want to: * Use a single lifetime where possible, as our HAL driver will "require" its parts for the same amount of time * Erase ALL peripheral instance generics, instead using runtime storage to store which instances are used for a given peripheral. * Retain a single generic for "Mode", typically `Blocking` or `Async`, where the latter is often interrupt-enabled and has async methods, while the former doesn't. This allows us to create a type that looks as follows: ```rust struct<'a, MODE> I2c { /* ... */ } type Example = I2C<'a, Async>; ``` In order to retain type safety functionality, we do still use the per-instance and per-peripheral generics, but ONLY at the constructor. This means that constructors will end up looking something like: ```rust impl<'a> I2c<'a, Blocking> { pub fn new( peri: Peri<'a, T>, scl: Peri<'a, impl SclPin>, sda: Peri<'a, impl SdaPin>, config: Config, ) -> Result, Error> { // get information like references/pointers to the specific // instance of the peripherals, or per-instance specific setup // // Get pointers for this instance of I2C let info = T::info(); // Perform GPIO-specific setup scl.setup_scl(); sda.setup_sda(); // If we needed to enable interrupts, this is likely bound to the generic // instance: // // T::Interrupt::unpend(); // ... Ok(I2c { info, // hold on to for later! // ... }) } } ``` #### Checking Errors When checking errors, ensure that ALL errors are cleared before returning. Otherwise early returns can lead to "stuck" errors. Instead of this: ```rust fn check_and_clear_rx_errors(info: &'static Info) -> Result<()> { let stat = info.regs().stat().read(); if stat.or() { info.regs().stat().write(|w| w.set_or(true)); Err(Error::Overrun) } else if stat.pf() { info.regs().stat().write(|w| w.set_pf(true)); Err(Error::Parity) } else if stat.fe() { info.regs().stat().write(|w| w.set_fe(true)); return Err(Error::Framing); } else if stat.nf() { info.regs().stat().write(|w| w.set_nf(true)); return Err(Error::Noise); } else { Ok(()) } } ``` Ensure that all errors are cleared: ```rust fn check_and_clear_rx_errors(info: &'static Info) -> Result<()> { let stat = info.regs().stat().read(); // Check for overrun first - other error flags are prevented when OR is set let or_set = stat.or(); let pf_set = stat.pf(); let fe_set = stat.fe(); let nf_set = stat.nf(); // Clear all errors before returning info.regs().stat().write(|w| { w.set_or(or_set); w.set_pf(pf_set); w.set_fe(fe_set); w.set_nf(nf_set); }); // Return error source if or_set { Err(Error::Overrun) } else if pf_set { Err(Error::Parity) } else if fe_set { Err(Error::Framing) } else if nf_set { Err(Error::Noise) } else { Ok(()) } } ``` #### Error types When creating `Error` types for each peripheral, consider the following high level guidance: ##### Splitting up the Error types Instead of making one top-level `Error` for the entire peripheral, it it often useful to create multiple error enums. For example, instead of: ```rust enum Error { Clocks(ClockError), BadConfig, Timeout, TransferTooLarge, } impl Example { // Can return `Err(Clocks)` or `Err(BadConfig)` pub fn new(config: Config) -> Result { /* ... */ } // Can return `Err(BadConfig)` or `Err(TransferTooLarge)` pub fn send_u8s(&mut self, mode: Mode, data: &[u8]) -> Result<(), Error> { /* ... */ } // Can return `Err(BadConfig)` or `Err(TransferTooLarge)` pub fn send_u16s(&mut self, mode: Mode, data: &[u16]) -> Result<(), Error> { /* ... */ } // Can return `Err(Timeout)` or `Err(TransferTooLarge)` pub fn recv(&mut self, data: &mut [u8]) -> Result { /* ... */ } } ``` If the same `Error` type is used, the user may need to `match` on errors that are "impossible", e.g. a `new()` function returning `Error::Timeout`. Instead, it might be worth splitting this into *three* errors: ```rust enum CreateError { Clocks(ClockError), BadConfig, } enum SendError { BadConfig, TransferTooLarge, } enum RecvError { Timeout, TransferTooLarge, } impl Example { pub fn new(config: Config) -> Result { /* ... */ } pub fn send_u8s(&mut self, mode: Mode, data: &[u8]) -> Result<(), SendError> { /* ... */ } pub fn send_u16s(&mut self, mode: Mode, data: &[u16]) -> Result<(), SendError> { /* ... */ } pub fn recv(&mut self, data: &mut [u8]) -> Result { /* ... */ } } ``` ##### Don't make a `Result` alias It *used* to be common to see module specific aliases for `Result`s, e.g.: ```rust pub type Result = Result; ``` However: * This can lead to confusion for users if they have multiple `Result`s in scope * It pushes for making "one `Error` per module", which is the opposite of what is described above ##### Mark errors as `#[non_exhaustive]` Unless we are **definitely** sure that we have covered all possible kinds of errors for a HAL driver, we should mark the `Error` type(s) as `#[non_exhaustive]`, to prevent making a breaking change when adding a new error type. For example: ```rust #[non_exhaustive] enum RecvError { Timeout, TransferTooLarge, } ``` #### Avoid Wildcard/Glob imports We generally want to avoid the use of wildcard/glob imports, like: ```rust use super::*; use other_module::*; ``` This can cause [surprising semver breakage], and make the code harder to read. [surprising semver breakage]: https://predr.ag/blog/breaking-semver-in-rust-by-adding-private-type-or-import/ ================================================ FILE: embassy-mcxa/README.md ================================================ # Embassy NXP MCX-A MCUs HAL A Hardware Abstraction Layer (HAL) for the NXP MCX-A family of microcontrollers using the Embassy async framework. This HAL provides safe, idiomatic Rust interfaces for GPIO, UART, and OSTIMER peripherals. ================================================ FILE: embassy-mcxa/src/adc.rs ================================================ //! ADC driver use core::marker::PhantomData; use core::ops::{Deref, RangeInclusive}; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitCell; use nxp_pac::adc::vals::{AdcActive, TcompIe, TcompInt}; use paste::paste; use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4, PreEnableParts}; use crate::clocks::{ClockError, Gate, PoweredClock, WakeGuard, enable_and_reset}; use crate::gpio::{AnyPin, GpioPin, SealedPin}; use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac::adc::vals::{ Avgs, CalAvgs, CalRdy, CalReq, Calofs, Cmpen, Dozen, Gcc0Rdy, HptExdi, Loop as HwLoop, Mode as ConvMode, Next, Pwrsel, Refsel, Rst, Rstfifo0, Sts, Tcmd, Tpri, Tprictrl, }; use crate::pac::port::vals::Mux; use crate::pac::{self}; /// Trigger priority policy for ADC conversions. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum TriggerPriorityPolicy { ConvPreemptImmediatelyNotAutoResumed = 0, ConvPreemptSoftlyNotAutoResumed = 1, ConvPreemptImmediatelyAutoRestarted = 4, ConvPreemptSoftlyAutoRestarted = 5, ConvPreemptImmediatelyAutoResumed = 12, ConvPreemptSoftlyAutoResumed = 13, ConvPreemptSubsequentlyNotAutoResumed = 2, ConvPreemptSubsequentlyAutoRestarted = 6, ConvPreemptSubsequentlyAutoResumed = 14, TriggerPriorityExceptionDisabled = 16, } /// The reference voltage used by the ADC #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[repr(u8)] pub enum ReferenceVoltage { #[default] VrefHReferencePin = 0b00, VrefI = 0b01, VddaAnaPin = 0b10, } /// Configuration for the LPADC peripheral. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Config { /// Control system transition to Stop and Wait power modes while /// ADC is converting. /// /// When enabled in Doze mode, immediate entries to Wait or Stop /// are allowed. /// /// When disabled, the ADC will wait for the current averaging /// iteration/FIFO storage to complete before acknowledging stop /// or wait mode entry. pub enable_in_doze_mode: bool, /// Auto-Calibration Averages. pub calibration_average_mode: CalAvgs, /// When true, the ADC analog circuits are pre-enabled and ready to execute /// conversions without startup delays (at the cost of higher DC /// current consumption). pub power_pre_enabled: bool, /// Power-up delay value (in ADC clock cycles) pub power_up_delay: u8, /// Reference voltage source selection pub reference_voltage_source: ReferenceVoltage, /// Power configuration selection. pub power_level_mode: Pwrsel, /// Trigger priority policy for handling multiple triggers pub trigger_priority_policy: TriggerPriorityPolicy, /// Controls the duration of pausing during command execution /// sequencing. The pause delay is a count of (convPauseDelay*4) /// ADCK cycles. /// /// The available value range is in 9-bit. /// When None, the pausing function is not enabled pub conv_pause_delay: Option, /// Power configuration (normal/deep sleep behavior) pub power: PoweredClock, /// ADC clock source selection pub source: AdcClockSel, /// Clock divider for ADC clock pub div: Div4, } impl Default for Config { fn default() -> Self { Self { enable_in_doze_mode: true, calibration_average_mode: CalAvgs::NO_AVERAGE, power_pre_enabled: false, power_up_delay: 0x80, reference_voltage_source: Default::default(), power_level_mode: Pwrsel::LOWEST, trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, conv_pause_delay: None, power: PoweredClock::NormalEnabledDeepSleepDisabled, source: AdcClockSel::FroLfDiv, div: Div4::no_div(), } } } /// The ID for a command #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CommandId { Cmd1 = 1, Cmd2 = 2, Cmd3 = 3, Cmd4 = 4, Cmd5 = 5, Cmd6 = 6, Cmd7 = 7, } impl From for CommandId { fn from(value: u8) -> Self { match value { 1 => Self::Cmd1, 2 => Self::Cmd2, 3 => Self::Cmd3, 4 => Self::Cmd4, 5 => Self::Cmd5, 6 => Self::Cmd6, 7 => Self::Cmd7, _ => unreachable!(), } } } /// Select the compare functionality of the ADC #[derive(Debug, Clone, PartialEq, Eq)] pub enum Compare { /// Do not perform compare operation. Always store the conversion result to the FIFO. Disabled, /// Store conversion result to FIFO at end /// of averaging only if compare is true. If compare is false do not store /// the result to the FIFO. In either the true or false condition, the LOOP /// setting is considered and increments the LOOP counter before deciding /// whether the current command has completed or additional LOOP /// iterations are required. StoreIf(CompareFunction), /// Store conversion result to FIFO at end of /// averaging only if compare is true. Once the true condition is found the /// LOOP setting is considered and increments the LOOP counter before /// deciding whether the current command has completed or additional /// LOOP iterations are required. If the compare is false do not store the /// result to the FIFO. The conversion is repeated without consideration of /// LOOP setting and does not increment the LOOP counter. SkipUntil(CompareFunction), } impl Compare { fn cmp_en(&self) -> Cmpen { match self { Compare::Disabled => Cmpen::DISABLED_ALWAYS_STORE_RESULT, Compare::StoreIf(_) => Cmpen::COMPARE_RESULT_STORE_IF_TRUE, Compare::SkipUntil(_) => Cmpen::COMPARE_RESULT_KEEP_CONVERTING_UNTIL_TRUE_STORE_IF_TRUE, } } /// Get the CVL & CVH values fn get_vals(&self) -> (u16, u16) { match self { Compare::Disabled => (0, 0), Compare::StoreIf(compare_function) | Compare::SkipUntil(compare_function) => compare_function.get_vals(), } } } /// Type that specifies the function used for the compare featue of the ADC. /// /// This determines the `CVL` & `CVH` values. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompareFunction { /// The compare will succeed when the value is *not* in the specified range OutsideRange(RangeInclusive), /// The compare will succeed when the value is lower than the specified value LessThan(u16), /// The compare will succeed when the value is higher than the specified value GreaterThan(u16), /// The compare will succeed when the value is in the specified range InsideRange(RangeInclusive), } impl CompareFunction { /// Get the CVL & CVH values fn get_vals(&self) -> (u16, u16) { match self { CompareFunction::OutsideRange(range) => { assert!(!range.is_empty()); (*range.start(), *range.end()) } CompareFunction::LessThan(val) => (*val, u16::MAX), CompareFunction::GreaterThan(val) => (0, *val), CompareFunction::InsideRange(range) => { assert!(!range.is_empty()); (*range.end(), *range.start()) } } } } enum Channels<'a, T> { Single([Peri<'a, AnyAdcPin>; 1]), Multi(&'a [Peri<'a, AnyAdcPin>]), } impl<'a, T> Deref for Channels<'a, T> { type Target = [Peri<'a, AnyAdcPin>]; fn deref(&self) -> &Self::Target { match self { Channels::Single(single) => single, Channels::Multi(multi) => multi, } } } /// A command that can be executed by the ADC pub struct Command<'a, T> { /// When true, if increment_channel: bool, /// The number of times the command is run. Range = `0..=15`. /// If [Self::increment_channel] is true, the repeats happen on different channels loop_count: u8, config: CommandConfig, channels: Channels<'a, T>, } impl<'a, T: Instance> Command<'a, T> { /// A command that does one conversion on a channel pub fn new_single(channel: Peri<'a, impl Into> + PeripheralType>, config: CommandConfig) -> Self { Self { increment_channel: false, loop_count: 0, config, channels: Channels::Single([channel.into()]), } } /// A command that does multiple conversions on a channel. /// - `num_loops`: The amount of times the command is run. Range: `1..=16` pub fn new_looping( channel: Peri<'a, impl Into> + PeripheralType>, num_loops: u8, config: CommandConfig, ) -> Result { if !(1..=16).contains(&num_loops) { return Err(Error::InvalidConfig); } Ok(Self { increment_channel: false, loop_count: num_loops - 1, config, channels: Channels::Single([channel.into()]), }) } /// A command that does multiple conversions on multiple channels pub fn new_multichannel(channels: &'a [Peri<'a, AnyAdcPin>], config: CommandConfig) -> Result { if !(1..=15).contains(&channels.len()) { return Err(Error::InvalidConfig); } let mut next_channel = channels[0].channel + 1; for pin in channels.iter().skip(1) { if pin.channel != next_channel { return Err(Error::InvalidConfig); } next_channel = pin.channel + 1; } Ok(Self { increment_channel: true, loop_count: channels.len() as u8, config, channels: Channels::Multi(channels), }) } } /// Configuration for a conversion command #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommandConfig { /// The command that will be executed next. /// /// If None, conversion will end pub chained_command: Option, /// The averaging done on a conversion pub averaging: Avgs, /// The sampling time of a conversion pub sample_time: Sts, /// The compare function being used pub compare: Compare, /// The resolution of a conversion pub resolution: ConvMode, /// When false, the command will not wait for a trigger once the command sequence has been started. /// When true, a trigger is required before the command is started. pub wait_for_trigger: bool, } impl Default for CommandConfig { fn default() -> Self { Self { chained_command: None, averaging: Avgs::NO_AVERAGE, sample_time: Sts::SAMPLE_3P5, compare: Compare::Disabled, resolution: ConvMode::DATA_12_BITS, wait_for_trigger: false, } } } /// Configuration for a conversion trigger. /// /// Defines how a trigger initiates ADC conversions. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Trigger { /// The command that is triggered by this trigger pub target_command_id: CommandId, pub delay_power: u8, /// The priority level of the trigger pub priority: Tpri, pub enable_hardware_trigger: bool, pub resync: bool, pub synchronous: bool, } impl Default for Trigger { fn default() -> Self { Trigger { target_command_id: CommandId::Cmd1, delay_power: 0, priority: Tpri::HIGHEST_PRIORITY, enable_hardware_trigger: false, resync: false, synchronous: false, } } } /// ADC Error types #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// FIFO is empty, no conversion result available FifoEmpty, /// FIFO is empty, but the adc is active and a new conversion will be ready soon FifoPending, /// Invalid configuration InvalidConfig, /// Too many commands TooManyCommands, /// Too many triggers TooManyTriggers, /// Tried to call a trigger that was not configured NoTrigger, /// Clock configuration error. ClockSetup(ClockError), } /// Result of an ADC conversion. /// /// Contains the conversion value and metadata about the conversion. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Conversion { /// The command that performed this conversion pub command: CommandId, /// For a looping command, the loop index. For a multichannel command, the channel index. pub loop_channel_index: u8, /// The trigger that triggered the command to run pub trigger_id_source: u8, /// The raw value from the ADC pub conv_value: u16, } /// ADC driver instance. pub struct Adc<'a, M: Mode> { commands: &'a [Command<'a, ()>], num_triggers: u8, info: &'static Info, _wg: Option, _mode: PhantomData, } impl<'a> Adc<'a, Blocking> { /// Create a new blocking instance of the ADC pub fn new_blocking( _inst: Peri<'a, T>, commands: &'a [Command<'a, T>], triggers: &[Trigger], config: Config, ) -> Result { // Safety: // We transmute the ADC instance to a `()`. This is fine since the `T` is only a phantomdata. // Because we're now in this function, we don't need this info anymore. let commands = unsafe { core::mem::transmute::<&[Command<'_, T>], &[Command<'_, ()>]>(commands) }; let parts = unsafe { enable_and_reset::(&AdcConfig { power: config.power, source: config.source, div: config.div, }) .map_err(Error::ClockSetup)? }; Self::new_inner(T::info(), commands, triggers, config, parts) } } impl<'a> Adc<'a, Async> { /// Create a new async instance of the ADC pub fn new_async( _inst: Peri<'a, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'a, commands: &'a [Command<'a, T>], triggers: &[Trigger], config: Config, ) -> Result { // Safety: // We transmute the ADC instance to a `()`. This is fine since the `T` is only a phantomdata. // Because we're now in this function, we don't need this info anymore. let commands = unsafe { core::mem::transmute::<&[Command<'_, T>], &[Command<'_, ()>]>(commands) }; let parts = unsafe { enable_and_reset::(&AdcConfig { power: config.power, source: config.source, div: config.div, }) .map_err(Error::ClockSetup)? }; let adc = Self::new_inner(T::info(), commands, triggers, config, parts)?; T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Ok(adc) } /// Reads the current conversion result from the fifo or waits for the next one if it's pending. /// /// If no conversion is pending, None is returned. pub async fn wait_get_conversion(&mut self) -> Option { self.info .wait_cell() .wait_for_value(|| { // Enable the interrupts. They get disabled in the interrupt handler self.info.regs().ie().write(|reg| { reg.set_fwmie0(true); reg.set_tcomp_ie(TcompIe::ALL_TRIGGER_COMPLETES_ENABLED); }); match self.try_get_conversion() { Ok(result) => Some(Some(result)), Err(Error::FifoPending) => None, Err(Error::FifoEmpty) => Some(None), _ => unreachable!(), } }) .await .unwrap() } /// Reads the current conversion result from the fifo or waits for the next one even if no conversion is currently pending. /// /// If no conversion is pending, None is returned. pub async fn wait_conversion(&mut self) -> Conversion { self.info .wait_cell() .wait_for_value(|| { // Enable the interrupts. They get disabled in the interrupt handler self.info.regs().ie().write(|reg| { reg.set_fwmie0(true); reg.set_tcomp_ie(TcompIe::ALL_TRIGGER_COMPLETES_ENABLED); }); match self.try_get_conversion() { Ok(result) => Some(result), Err(Error::FifoPending) => None, Err(Error::FifoEmpty) => None, _ => unreachable!(), } }) .await .unwrap() } } impl<'a, M: Mode> Adc<'a, M> { /// Trigger ADC conversion(s) via software. /// /// Initiates conversion(s) for the trigger(s) specified in the bitmask. /// Each bit in the mask corresponds to a trigger ID (bit 0 = trigger 0, etc.). /// /// # Arguments /// * `trigger_id_mask` - Bitmask of trigger IDs to activate (bit N = trigger N) /// /// # Returns /// * `Ok(())` if the triger mask was valid /// * [Error::NoTrigger] if the mask is calling a trigger that's not configured pub fn do_software_trigger(&mut self, trigger_id_mask: u8) -> Result<(), Error> { if (8 - trigger_id_mask.leading_zeros()) > self.num_triggers as u32 { return Err(Error::NoTrigger); } self.info.regs().swtrig().write(|w| w.0 = trigger_id_mask as u32); Ok(()) } /// Reset the FIFO buffer. /// /// Clears all pending conversion results from the FIFO. pub fn do_reset_fifo(&mut self) { self.info .regs() .ctrl() .modify(|w| w.set_rstfifo0(Rstfifo0::TRIGGER_RESET)); } /// Get conversion result from FIFO. /// /// Returns: /// - `Ok(ConvResult)` if a result is available /// - [Error::FifoEmpty] if the FIFO is empty /// - [Error::FifoPending] if the FIFO is empty, but the adc is active pub fn try_get_conversion(&mut self) -> Result { if self.info.regs().fctrl0().read().fcount() == 0 { if self.info.regs().stat().read().adc_active() == AdcActive::BUSY { return Err(Error::FifoPending); } return Err(Error::FifoEmpty); } let fifo = self.info.regs().resfifo0().read(); Ok(Conversion { command: (fifo.cmdsrc() as u8).into(), loop_channel_index: fifo.loopcnt() as u8, trigger_id_source: fifo.tsrc() as u8, conv_value: fifo.d(), }) } fn new_inner( info: &'static Info, commands: &'a [Command<'a, ()>], triggers: &[Trigger], config: Config, parts: PreEnableParts, ) -> Result { if commands.len() > 7 { return Err(Error::TooManyCommands); } if triggers.len() > 4 { return Err(Error::TooManyTriggers); } // Commands must only chain other existing commands if commands.iter().any(|c| { c.config .chained_command .is_some_and(|cc| (cc as u8 - 1) >= commands.len() as u8) }) { return Err(Error::InvalidConfig); } // Triggers must only target existing commands if triggers .iter() .any(|t| (t.target_command_id as u8 - 1) >= commands.len() as u8) { return Err(Error::InvalidConfig); } let adc = info.regs(); /* Reset the module. */ adc.ctrl().modify(|w| w.set_rst(Rst::HELD_IN_RESET)); adc.ctrl().modify(|w| w.set_rst(Rst::RELEASED_FROM_RESET)); adc.ctrl().modify(|w| w.set_rstfifo0(Rstfifo0::TRIGGER_RESET)); /* Disable the module before setting configuration. */ adc.ctrl().modify(|w| w.set_adcen(false)); /* Configure the module generally. */ adc.ctrl().modify(|w| { w.set_dozen(if config.enable_in_doze_mode { Dozen::ENABLED } else { Dozen::DISABLED }) }); /* Set calibration average mode. */ adc.ctrl().modify(|w| w.set_cal_avgs(config.calibration_average_mode)); adc.cfg().write(|w| { w.set_pwren(config.power_pre_enabled); w.set_pudly(config.power_up_delay); w.set_refsel(Refsel::from_bits(config.reference_voltage_source as u8)); w.set_pwrsel(config.power_level_mode); w.set_tprictrl(match config.trigger_priority_policy { TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FINISH_CURRENT_ON_PRIORITY, TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FINISH_SEQUENCE_ON_PRIORITY, _ => Tprictrl::ABORT_CURRENT_ON_PRIORITY, }); w.set_tres(matches!( config.trigger_priority_policy, TriggerPriorityPolicy::ConvPreemptImmediatelyAutoRestarted | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed )); w.set_tcmdres(matches!( config.trigger_priority_policy, TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled )); w.set_hpt_exdi(match config.trigger_priority_policy { TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => HptExdi::DISABLED, _ => HptExdi::ENABLED, }); }); if let Some(pause_delay) = config.conv_pause_delay { adc.pause().write(|w| { w.set_pauseen(true); w.set_pausedly(pause_delay); }); } else { adc.pause().write(|w| w.set_pauseen(false)); } // Set fifo watermark level to 0, so any data will trigger the possible interrupt adc.fctrl0().write(|w| w.set_fwmark(0)); for (index, command) in commands.iter().enumerate() { for channel in command.channels.deref() { channel.mux(); } let cmdl = adc.cmdl(index); let cmdh = adc.cmdh(index); cmdl.write(|w| { w.set_adch(command.channels[0].channel); w.set_mode(command.config.resolution); }); cmdh.write(|w| { w.set_next(Next::from_bits( command.config.chained_command.map(|cc| cc as u8).unwrap_or_default(), )); w.set_loop_(HwLoop::from_bits(command.loop_count)); w.set_avgs(command.config.averaging); w.set_sts(command.config.sample_time); w.set_cmpen(command.config.compare.cmp_en()); w.set_wait_trig(command.config.wait_for_trigger); w.set_lwi(command.increment_channel); }); info.regs().cv(index).write(|reg| { let (cvl, cvh) = command.config.compare.get_vals(); reg.set_cvl(cvl); reg.set_cvh(cvh); }); } for (index, trigger) in triggers.iter().enumerate() { let tctrl = adc.tctrl(index); tctrl.write(|w| { w.set_tcmd(Tcmd::from_bits(trigger.target_command_id as u8)); w.set_tdly(trigger.delay_power); w.set_tpri(trigger.priority); w.set_hten(trigger.enable_hardware_trigger); w.set_rsync(trigger.resync); w.set_tsync(trigger.synchronous); }); } // Enable ADC adc.ctrl().modify(|w| w.set_adcen(true)); Ok(Self { commands, num_triggers: triggers.len() as u8, info, _wg: parts.wake_guard, _mode: PhantomData, }) } /// Perform offset calibration. /// Waits for calibration to complete before returning. pub fn do_offset_calibration(&mut self) { // Enable calibration mode self.info .regs() .ctrl() .modify(|w| w.set_calofs(Calofs::OFFSET_CALIBRATION_REQUEST_PENDING)); // Wait for calibration to complete (polling status register) while self.info.regs().stat().read().cal_rdy() == CalRdy::NOT_SET {} } /// Calculate gain conversion result from gain adjustment factor. /// /// # Arguments /// * `gain_adjustment` - Gain adjustment factor /// /// # Returns /// Gain calibration register value fn get_gain_conv_result(&mut self, mut gain_adjustment: f32) -> u32 { let mut gcra_array = [0u32; 17]; let mut gcalr: u32 = 0; for i in (1..=17).rev() { let shift = 16 - (i - 1); let step = 1.0 / (1u32 << shift) as f32; let tmp = (gain_adjustment / step) as u32; gcra_array[i - 1] = tmp; gain_adjustment -= tmp as f32 * step; } for i in (1..=17).rev() { gcalr += gcra_array[i - 1] << (i - 1); } gcalr } /// Perform automatic gain calibration. pub fn do_auto_calibration(&mut self) { self.info .regs() .ctrl() .modify(|w| w.set_cal_req(CalReq::CALIBRATION_REQUEST_PENDING)); while self.info.regs().gcc0().read().rdy() == Gcc0Rdy::GAIN_CAL_NOT_VALID {} let mut gcca = self.info.regs().gcc0().read().gain_cal() as u32; if gcca & 0x8000 != 0 { gcca |= !0xFFFF; } let gcra = 131072.0 / (131072.0 - gcca as f32); // Write to GCR0 self.info.regs().gcr0().write(|w| w.0 = self.get_gain_conv_result(gcra)); self.info.regs().gcr0().modify(|w| w.set_rdy(true)); // Wait for calibration to complete (polling status register) while self.info.regs().stat().read().cal_rdy() == CalRdy::NOT_SET {} } } impl<'a, M: Mode> Drop for Adc<'a, M> { fn drop(&mut self) { // Turn off the ADC self.info.regs().ctrl().modify(|reg| reg.set_adcen(false)); // Demux all the pins for command in self.commands { for channel in command.channels.deref() { channel.demux(); } } } } /// ADC interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); T::info().regs().ie().write(|_| {}); // Stat tcomp should go to 0 when `ie` is disabled, but it doesn't. // So we have to do it manually. Errata? T::info() .regs() .stat() .write(|reg| reg.set_tcomp_int(TcompInt::COMPLETION_DETECTED)); T::info().wait_cell().wake(); } } mod sealed { /// Seal a trait pub trait Sealed {} /// Sealed pin trait pub trait SealedAdcPin {} } struct Info { regs: pac::adc::Adc, wait_cell: WaitCell, } unsafe impl Sync for Info {} impl Info { #[inline(always)] fn regs(&self) -> pac::adc::Adc { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } trait SealedInstance: Gate { fn info() -> &'static Info; const PERF_INT_INCR: fn(); } /// ADC Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType { /// Interrupt for this ADC instance. type Interrupt: Interrupt; } macro_rules! impl_instance { ($($n:expr),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], wait_cell: WaitCell::new(), }; &INFO } const PERF_INT_INCR: fn() = crate::perf_counters::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0, 1); /// Trait implemented by any possible ADC pin pub trait AdcPin: sealed::SealedAdcPin + GpioPin + PeripheralType { /// The channel to be used fn channel(&self) -> u8; /// Degrade the pin into an [AnyAdcPin] fn degrade(self) -> AnyAdcPin { let channel = self.channel(); AnyAdcPin { channel, pin: Some(GpioPin::degrade(self)), _phantom: PhantomData, } } } /// A type-erased ADC pin pub struct AnyAdcPin { channel: u8, pin: Option, _phantom: PhantomData, } impl AnyAdcPin { #[inline] fn mux(&self) { if let Some(pin) = &self.pin { // Set to digital GPIO with input buffer disabled and no pull-ups. // TODO also clear digital output value? pin.set_pull(crate::gpio::Pull::Disabled); pin.set_slew_rate(crate::gpio::SlewRate::Fast.into()); pin.set_drive_strength(crate::gpio::DriveStrength::Normal.into()); pin.set_function(Mux::MUX0); } } #[inline] fn demux(&self) { if let Some(pin) = &self.pin { pin.set_as_disabled() } } /// Get the internal temperature sensor pin pub fn temperature() -> Peri<'static, Self> { // Safety: The temp sensor doesn't gate or own anything, so it's fine to give out as many as the user asks unsafe { Peri::new_unchecked(Self { channel: 26, pin: None, _phantom: PhantomData, }) } } } impl> From

for AnyAdcPin { fn from(value: P) -> Self { AdcPin::degrade(value) } } embassy_hal_internal::impl_peripheral!(AnyAdcPin); /// Driver mode. #[allow(private_bounds)] pub trait Mode: sealed::Sealed {} /// Blocking mode. pub struct Blocking; impl sealed::Sealed for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl sealed::Sealed for Async {} impl Mode for Async {} macro_rules! impl_pin { ($pin:ident, $peri:ident, $channel:literal) => { impl sealed::SealedAdcPin for crate::peripherals::$pin {} impl AdcPin for crate::peripherals::$pin { #[inline] fn channel(&self) -> u8 { $channel } } }; } #[cfg(feature = "mcxa2xx")] mod mcxa2xx_pins { use super::*; impl_pin!(P2_0, ADC0, 0); impl_pin!(P2_4, ADC0, 1); impl_pin!(P2_15, ADC0, 2); impl_pin!(P2_3, ADC0, 3); impl_pin!(P2_2, ADC0, 4); impl_pin!(P2_12, ADC0, 5); impl_pin!(P2_16, ADC0, 6); impl_pin!(P2_7, ADC0, 7); impl_pin!(P0_18, ADC0, 8); impl_pin!(P0_19, ADC0, 9); impl_pin!(P0_20, ADC0, 10); impl_pin!(P0_21, ADC0, 11); impl_pin!(P0_22, ADC0, 12); impl_pin!(P0_23, ADC0, 13); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_3, ADC0, 14); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_6, ADC0, 15); impl_pin!(P1_0, ADC0, 16); impl_pin!(P1_1, ADC0, 17); impl_pin!(P1_2, ADC0, 18); impl_pin!(P1_3, ADC0, 19); impl_pin!(P1_4, ADC0, 20); impl_pin!(P1_5, ADC0, 21); impl_pin!(P1_6, ADC0, 22); impl_pin!(P1_7, ADC0, 23); // ??? // impl_pin!(P1_10, ADC0, 255); impl_pin!(P2_1, ADC1, 0); impl_pin!(P2_5, ADC1, 1); impl_pin!(P2_19, ADC1, 2); impl_pin!(P2_6, ADC1, 3); impl_pin!(P2_3, ADC1, 4); impl_pin!(P2_13, ADC1, 5); impl_pin!(P2_17, ADC1, 6); impl_pin!(P2_7, ADC1, 7); impl_pin!(P1_10, ADC1, 8); impl_pin!(P1_11, ADC1, 9); impl_pin!(P1_12, ADC1, 10); impl_pin!(P1_13, ADC1, 11); impl_pin!(P1_14, ADC1, 12); impl_pin!(P1_15, ADC1, 13); // ??? // impl_pin!(P1_16, ADC1, 255); // impl_pin!(P1_17, ADC1, 255); // impl_pin!(P1_18, ADC1, 255); // impl_pin!(P1_19, ADC1, 255); // ??? impl_pin!(P3_31, ADC1, 20); impl_pin!(P3_30, ADC1, 21); impl_pin!(P3_29, ADC1, 22); } #[cfg(feature = "mcxa5xx")] mod mcxa5xx_pins { use super::*; #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_3, ADC0, 14); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_6, ADC0, 15); impl_pin!(P0_14, ADC0, 10); impl_pin!(P0_15, ADC0, 11); impl_pin!(P0_18, ADC0, 8); impl_pin!(P0_19, ADC0, 9); impl_pin!(P0_22, ADC0, 12); impl_pin!(P0_23, ADC0, 13); impl_pin!(P1_0, ADC0, 16); impl_pin!(P1_1, ADC0, 17); impl_pin!(P1_2, ADC0, 18); impl_pin!(P1_3, ADC0, 19); impl_pin!(P1_4, ADC0, 20); impl_pin!(P1_5, ADC0, 21); impl_pin!(P1_6, ADC0, 22); impl_pin!(P1_7, ADC0, 23); impl_pin!(P1_10, ADC1, 8); impl_pin!(P1_11, ADC1, 9); impl_pin!(P1_12, ADC1, 10); impl_pin!(P1_13, ADC1, 11); impl_pin!(P1_14, ADC1, 12); impl_pin!(P1_15, ADC1, 13); impl_pin!(P1_16, ADC1, 14); impl_pin!(P1_17, ADC1, 15); impl_pin!(P1_18, ADC1, 16); impl_pin!(P1_19, ADC1, 17); impl_pin!(P2_0, ADC0, 0); impl_pin!(P2_1, ADC1, 0); impl_pin!(P2_2, ADC0, 4); impl_pin!(P2_3, ADC0, 3); impl_pin!(P2_3, ADC1, 4); impl_pin!(P2_4, ADC0, 1); impl_pin!(P2_5, ADC1, 1); impl_pin!(P2_6, ADC1, 3); impl_pin!(P2_7, ADC0, 7); impl_pin!(P2_7, ADC1, 7); impl_pin!(P2_12, ADC0, 5); impl_pin!(P2_13, ADC1, 5); impl_pin!(P2_15, ADC0, 2); impl_pin!(P2_16, ADC0, 6); impl_pin!(P2_17, ADC1, 6); impl_pin!(P2_19, ADC1, 2); #[cfg(feature = "rosc-32k-as-gpio")] impl_pin!(P5_0, ADC1, 20); #[cfg(feature = "rosc-32k-as-gpio")] impl_pin!(P5_1, ADC1, 21); impl_pin!(P5_2, ADC1, 22); } ================================================ FILE: embassy-mcxa/src/cdog.rs ================================================ //! Code Watchdog (CDOG) driver for MCXA microcontrollers. //! //! The CDOG is a hardware watchdog that monitors code execution flow by tracking //! a secure counter value and execute a timer. It can detect various fault conditions including: //! - Timeout: Code execution takes too long //! - Miscompare: Secure counter value doesn't match expected value //! - Sequence: Invalid operation sequence //! - State: Invalid state transitions //! - Address: Invalid memory access use embassy_hal_internal::Peri; use embassy_hal_internal::interrupt::InterruptExt; use crate::interrupt::typelevel::{self, Handler}; use crate::pac::cdog::vals::{Ctrl, DebugHaltCtrl, IrqPause, LockCtrl}; use crate::pac::{self}; use crate::peripherals::CDOG0; /// Shorthand for `Result`. pub type Result = core::result::Result; /// Errors that can occur when configuring or using the CDOG. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// Watchdog is currently running and cannot be reconfigured WatchdogRunning, } /// Fault control configuration for different fault types. /// /// Determines what action the CDOG takes when a fault is detected. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[repr(u8)] pub enum FaultControl { /// Enable system reset on fault detection EnableReset = 1, /// Enable interrupt on fault detection EnableInterrupt = 2, #[default] /// Disable both reset and interrupt DisableBoth = 4, } impl From for Ctrl { fn from(val: FaultControl) -> Self { match val { FaultControl::EnableReset => Ctrl::ENABLE_RESET, FaultControl::EnableInterrupt => Ctrl::ENABLE_INTERRUPT, FaultControl::DisableBoth => Ctrl::DISABLE_BOTH, } } } /// Timer pause control during special conditions. /// /// Controls whether the instruction timer continues running or pauses. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[repr(u8)] pub enum PauseControl { #[default] /// Keep timer running during IRQ or debug halt RunTimer = 1, /// Pause timer during IRQ or debug halt PauseTimer = 2, } /// Lock control for CDOG configuration. /// /// When locked, configuration registers cannot be modified. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[repr(u8)] pub enum LockControl { /// Lock configuration Locked = 1, #[default] /// Unlock configuration Unlocked = 2, } /// CDOG (Code Watchdog) configuration structure. /// /// Defines the behavior of the watchdog for various fault conditions /// and operational modes. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Config { /// The timeout period after which the watchdog will trigger pub timeout: FaultControl, pub miscompare: FaultControl, pub sequence: FaultControl, pub state: FaultControl, pub address: FaultControl, pub irq_pause: PauseControl, pub debug_halt: PauseControl, pub lock: LockControl, } /// Code Watchdog peripheral pub struct Watchdog<'d> { _peri: Peri<'d, CDOG0>, // The register block of the CDOG instance info: pac::cdog::Cdog, /// Software-tracked secure counter value secure_counter: u32, } impl<'d> Watchdog<'d> { /// Creates a new CDOG instance with the given configuration. /// /// # Arguments /// * `_peri` - Peripheral ownership token /// * `_irq` - Interrupt binding for CDOG0 interrupt handler /// * `config` - Configuration for fault handling and operational modes /// /// # Returns /// * `Ok(Watchdog)` - Successfully configured watchdog /// * `Err(Error::WatchdogRunning)` - Watchdog is already running and cannot be reconfigured pub fn new( _peri: Peri<'d, CDOG0>, _irq: impl crate::interrupt::typelevel::Binding + 'd, config: Config, ) -> Result { let info = pac::CDOG0; // Ensure that CDOG is in IDLE mode otherwise writing to CONTROL register will trigger a fault. if info.status().read().curst() == 0xA { return Err(Error::WatchdogRunning); } // Clear all pending error flags to prevent immediate reset after enable. // The clearing method depends on whether the module is locked: // - Unlocked (LOCK_CTRL = 10b): Write flag values directly // - Locked (LOCK_CTRL = 01b): Write '1' to clear individual flags let b = info.control().read().lock_ctrl() == LockCtrl::LOCKED; // Locked mode: write '1' to clear each flag info.flags().write(|w| { w.set_to_flag(b); w.set_miscom_flag(b); w.set_seq_flag(b); w.set_cnt_flag(b); w.set_state_flag(b); w.set_addr_flag(b); w.set_por_flag(b); }); // Configure CONTROL register with the provided config info.control().write(|w| { w.set_timeout_ctrl(config.timeout.into()); w.set_miscompare_ctrl(config.miscompare.into()); w.set_sequence_ctrl(config.sequence.into()); w.set_state_ctrl(config.state.into()); w.set_address_ctrl(config.address.into()); // IRQ pause control match config.irq_pause { PauseControl::RunTimer => w.set_irq_pause(IrqPause::RUN_TIMER), PauseControl::PauseTimer => w.set_irq_pause(IrqPause::PAUSE_TIMER), }; // Debug halt control match config.debug_halt { PauseControl::RunTimer => w.set_debug_halt_ctrl(DebugHaltCtrl::RUN_TIMER), PauseControl::PauseTimer => w.set_debug_halt_ctrl(DebugHaltCtrl::PAUSE_TIMER), }; // Lock control match config.lock { LockControl::Locked => w.set_lock_ctrl(LockCtrl::LOCKED), LockControl::Unlocked => w.set_lock_ctrl(LockCtrl::UNLOCKED), } }); crate::pac::Interrupt::CDOG0.unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { crate::pac::Interrupt::CDOG0.enable() }; Ok(Self { _peri, info, secure_counter: 0, }) } /// Starts the watchdog with specified timer and counter values. /// /// # Arguments /// * `instruction_timer_value` - Number of clock cycles before timeout /// * `secure_counter_value` - Initial value of the secure counter /// /// # Note /// If the watchdog is already running, this will stop it first. pub fn start(&mut self, instruction_timer_value: u32, secure_counter_value: u32) { // Ensure the CDOG is in IDLE mode before starting // Status value 0xA indicates ACTIVE state while self.info.status().read().curst() == 0xA { self.stop(); } // Update internal secure counter tracking self.secure_counter = secure_counter_value; // Set the instruction timer reload value (timeout period) self.info.reload().write(|w| w.set_rload(instruction_timer_value)); // Start the watchdog with initial secure counter value self.info.start().write(|w| w.set_strt(secure_counter_value)); } /// Adds a value to the secure counter. /// /// # Arguments /// * `add` - Value to add to the secure counter pub fn add(&mut self, add: u32) { self.secure_counter = self.secure_counter.wrapping_add(add); self.info.add().write(|w| w.set_ad(add)); } // Subtracts a value from the secure counter. /// /// # Arguments /// * `sub` - Value to subtract from the secure counter pub fn sub(&mut self, sub: u32) { self.secure_counter = self.secure_counter.wrapping_sub(sub); self.info.sub().write(|w| w.set_sb(sub)); } /// Checks the secure counter value and restarts the watchdog. /// /// This stops the watchdog, verifies the secure counter matches the expected /// value, and restarts with the same instruction timer reload value. /// /// # Arguments /// * `check` - Expected secure counter value /// /// # Note /// If the counter doesn't match, a miscompare fault may be triggered /// depending on configuration. pub fn check(&mut self, check: u32) { self.secure_counter = check; self.info.stop().write(|w| w.set_stp(self.secure_counter)); let reload = self.info.reload().read().rload(); self.info.reload().write(|w| w.set_rload(reload)); self.info.start().write(|w| w.set_strt(self.secure_counter)); } /// Stops the watchdog timer. /// /// # Note /// This is a private method. The watchdog is stopped by writing the /// current secure counter value to the STOP register. fn stop(&mut self) { self.info.stop().write(|w| w.set_stp(self.secure_counter)); } /// Reads the current instruction timer value. /// /// # Returns /// Current countdown value of the instruction timer. pub fn get_instruction_timer(&self) -> u32 { self.info.instruction_timer().read().instim() } // Gets the current secure counter value. /// /// # Returns /// The software-tracked secure counter value. pub fn get_secure_counter(&self) -> u32 { self.secure_counter } /// Updates the instruction timer reload value. /// /// # Arguments /// * `instruction_timer_value` - New timeout period in clock cycles pub fn update_instruction_timer(&mut self, instruction_timer_value: u32) { self.info.reload().write(|w| w.set_rload(instruction_timer_value)); } /// Sets a persistent value in the CDOG peripheral. /// /// This value is stored in the 32 bits PERSISTENT register and persist through resets other than a Power-On Reset (POR). /// /// # Arguments /// * `value` - The 32-bit value to store in the persistent register pub fn set_persistent_value(&mut self, value: u32) { self.info.persistent().write(|w| w.set_persis(value)); } /// Gets the persistent value from the CDOG peripheral. /// /// # Returns /// The 32-bit value stored in the persistent register pub fn get_persistent_value(&self) -> u32 { self.info.persistent().read().persis() } } /// CDOG0 interrupt handler. /// /// This handler is called when any cdog interrupt fires. /// When reset happens, the interrupt handler will never be reached. pub struct InterruptHandler; impl Handler for InterruptHandler { unsafe fn on_interrupt() { crate::perf_counters::incr_interrupt_cdog0(); let cdog0 = pac::CDOG0; // Print all flags at once using the Debug implementation #[cfg(feature = "defmt")] defmt::trace!("CDOG0 flags {}", cdog0.flags().read()); // Stop the cdog cdog0.stop().write(|w| w.set_stp(0)); // Clear all flags by writing 0 cdog0.flags().write(|w| w.0 = 0); } } ================================================ FILE: embassy-mcxa/src/chips/mcxa2xx.rs ================================================ //! Module for MCXA2xx family pub use inner_periph::{Peripherals, peripherals}; use crate::interrupt::InterruptExt; // NOTE: macro generates missing safety docs and unsafe calls in unsafe blocks, // allow for now, and put in a module so we can apply the rule to that scope. #[allow(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] mod inner_periph { #[rustfmt::skip] embassy_hal_internal::peripherals!( ADC0, ADC1, AOI0, AOI1, CAN0, CDOG0, CDOG1, // CLKOUT is not specifically a peripheral (it's part of SYSCON), // but we still want it to be a singleton. CLKOUT, CMC, CMP0, CMP1, CRC0, CTIMER0, CTIMER0_CH0, CTIMER0_CH1, CTIMER0_CH2, CTIMER0_CH3, CTIMER1, CTIMER1_CH0, CTIMER1_CH1, CTIMER1_CH2, CTIMER1_CH3, CTIMER2, CTIMER2_CH0, CTIMER2_CH1, CTIMER2_CH2, CTIMER2_CH3, CTIMER3, CTIMER3_CH0, CTIMER3_CH1, CTIMER3_CH2, CTIMER3_CH3, CTIMER4, CTIMER4_CH0, CTIMER4_CH1, CTIMER4_CH2, CTIMER4_CH3, DBGMAILBOX, DMA0, DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3, DMA_CH4, DMA_CH5, DMA_CH6, DMA_CH7, EDMA0_TCD0, EIM0, EQDC0, EQDC1, ERM0, FLEXIO0, FLEXPWM0, FLEXPWM1, FMC0, FMU0, FREQME0, GLIKEY0, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, I3C0, INPUTMUX0, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPSPI0, LPSPI1, LPTMR0, LPUART0, LPUART1, LPUART2, LPUART3, LPUART4, MAU0, MBC0, MRCC0, OPAMP0, OSTIMER0, // Normally SWDIO! #[cfg(feature = "swd-as-gpio")] P0_0, // Normally SWCLK! #[cfg(feature = "swd-as-gpio")] P0_1, // Normally SWO! #[cfg(feature = "swd-swo-as-gpio")] P0_2, // Normally JTAG TDI! #[cfg(feature = "jtag-extras-as-gpio")] P0_3, P0_4, P0_5, // Normally JTAG ISPMODE_N! #[cfg(feature = "jtag-extras-as-gpio")] P0_6, P0_7, P0_8, P0_9, 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, P1_0, P1_1, P1_2, P1_3, P1_4, P1_5, P1_6, P1_7, P1_8, P1_9, P1_10, P1_11, P1_12, P1_13, P1_14, P1_15, P1_16, P1_17, P1_18, P1_19, P1_20, P1_21, P1_22, P1_23, P1_24, P1_25, P1_26, P1_27, P1_28, #[cfg(feature = "dangerous-reset-as-gpio")] P1_29, #[cfg(feature = "sosc-as-gpio")] P1_30, #[cfg(feature = "sosc-as-gpio")] P1_31, P2_0, P2_1, P2_2, P2_3, P2_4, P2_5, P2_6, P2_7, P2_8, P2_9, P2_10, P2_11, P2_12, P2_13, P2_14, P2_15, P2_16, P2_17, P2_18, P2_19, P2_20, P2_21, P2_22, P2_23, P2_24, P2_25, P2_26, P2_27, P2_28, P2_29, P2_30, P2_31, P3_0, P3_1, P3_2, P3_3, P3_4, P3_5, P3_6, P3_7, P3_8, P3_9, P3_10, P3_11, P3_12, P3_13, P3_14, P3_15, P3_16, P3_17, P3_18, P3_19, P3_20, P3_21, P3_22, P3_23, P3_24, P3_25, P3_26, P3_27, P3_28, P3_29, P3_30, P3_31, P4_0, P4_1, P4_2, P4_3, P4_4, P4_5, P4_6, P4_7, P4_8, P4_9, P4_10, P4_11, P4_12, P4_13, P4_14, P4_15, P4_16, P4_17, P4_18, P4_19, P4_20, P4_21, P4_22, P4_23, P4_24, P4_25, P4_26, P4_27, P4_28, P4_29, P4_30, P4_31, P5_0, P5_1, P5_2, P5_3, P5_4, P5_5, P5_6, P5_7, P5_8, P5_9, P5_10, P5_11, P5_12, P5_13, P5_14, P5_15, P5_16, P5_17, P5_18, P5_19, P5_20, P5_21, P5_22, P5_23, P5_24, P5_25, P5_26, P5_27, P5_28, P5_29, P5_30, P5_31, PKC0, PORT0, PORT1, PORT2, PORT3, PORT4, RTC0, SAU, SCG0, SCN_SCB, SGI0, SMARTDMA0, SPC0, SYSCON, TDET0, TRNG0, UDF0, USB0, UTICK0, VBAT0, WAKETIMER0, WUU0, WWDT0, ); } // NOTE: Macro has missing safety docs and makes unsafe calls in unsafe fns pub use inner_interrupt::*; #[allow(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] mod inner_interrupt { embassy_hal_internal::interrupt_mod!( ADC0, ADC1, CAN0, CDOG0, CDOG1, CMC, CMP0, CMP1, CTIMER0, CTIMER1, CTIMER2, CTIMER3, CTIMER4, DAC0, DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3, DMA_CH4, DMA_CH5, DMA_CH6, DMA_CH7, EQDC0_COMPARE, EQDC0_HOME, EQDC0_INDEX, EQDC0_WATCHDOG, EQDC1_COMPARE, EQDC1_HOME, EQDC1_INDEX, EQDC1_WATCHDOG, ERM0_MULTI_BIT, ERM0_SINGLE_BIT, FLEXIO, FLEXPWM0_FAULT, FLEXPWM0_RELOAD_ERROR, FLEXPWM0_SUBMODULE0, FLEXPWM0_SUBMODULE1, FLEXPWM0_SUBMODULE2, FLEXPWM0_SUBMODULE3, FLEXPWM1_FAULT, FLEXPWM1_RELOAD_ERROR, FLEXPWM1_SUBMODULE0, FLEXPWM1_SUBMODULE1, FLEXPWM1_SUBMODULE2, FLEXPWM1_SUBMODULE3, FMU0, FREQME0, GLIKEY0, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, I3C0, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPSPI0, LPSPI1, LPTMR0, LPUART0, LPUART1, LPUART2, LPUART3, LPUART4, MAU, MBC0, OS_EVENT, PKC, RTC, RTC_1HZ, SCG0, SGI, SMARTDMA, SPC0, TDET, TRNG0, USB0, UTICK0, WAKETIMER0, WUU0, WWDT0, ); } /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). pub fn init(cfg: crate::config::Config) -> Peripherals { // Might not need to be mutable if none of the `...-as-gpio` features are active. #[allow(unused_mut)] let mut peripherals = Peripherals::take(); crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); crate::interrupt::GPIO0.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO1.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO2.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO3.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO4.set_priority(cfg.gpio_interrupt_priority); // Configure clocks crate::clocks::init(cfg.clock_cfg).unwrap(); // Initialize embassy-time global driver backed by OSTIMER0 // NOTE: As early as possible, but MUST be AFTER clocks! crate::ostimer::init(cfg.time_interrupt_priority); // Initialize the INPUTMUX peripheral crate::inputmux::init(); // Enable interrupts unsafe { cortex_m::interrupt::enable(); } // Initialize DMA controller (clock, reset, configuration) crate::dma::init(); // Enable GPIO clocks unsafe { _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); } // import may be unused if none of the following features are set #[allow(unused_imports)] use crate::gpio::SealedPin; // If we are not using SWD pins for SWD reasons, make them floating inputs #[cfg(feature = "swd-as-gpio")] { peripherals.P0_0.set_as_disabled(); peripherals.P0_1.set_as_disabled(); } #[cfg(feature = "swd-swo-as-gpio")] { peripherals.P0_2.set_as_disabled(); } #[cfg(feature = "jtag-extras-as-gpio")] { peripherals.P0_3.set_as_disabled(); peripherals.P0_6.set_as_disabled(); } #[cfg(feature = "dangerous-reset-as-gpio")] { // DANGER DANGER DANGER peripherals.P1_29.set_as_disabled(); } peripherals } // Chip specific GPIO impls mod gpio_impls { use crate::gpio::{AnyPin, GpioPin, Pull, SealedPin}; use crate::impl_pin; use crate::pac::common::{RW, Reg}; use crate::pac::gpio::vals::{Pdd, Pid}; use crate::pac::port::regs::Pcr; use crate::pac::port::vals::{Dse, Ibe, Mux, Sre}; #[cfg(feature = "swd-as-gpio")] impl_pin!(P0_0, 0, 0, GPIO0); #[cfg(feature = "swd-as-gpio")] impl_pin!(P0_1, 0, 1, GPIO0); #[cfg(feature = "swd-swo-as-gpio")] impl_pin!(P0_2, 0, 2, GPIO0); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_3, 0, 3, GPIO0); impl_pin!(P0_4, 0, 4, GPIO0); impl_pin!(P0_5, 0, 5, GPIO0); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_6, 0, 6, GPIO0); impl_pin!(P0_7, 0, 7, GPIO0); impl_pin!(P0_8, 0, 8, GPIO0); impl_pin!(P0_9, 0, 9, GPIO0); impl_pin!(P0_10, 0, 10, GPIO0); impl_pin!(P0_11, 0, 11, GPIO0); impl_pin!(P0_12, 0, 12, GPIO0); impl_pin!(P0_13, 0, 13, GPIO0); impl_pin!(P0_14, 0, 14, GPIO0); impl_pin!(P0_15, 0, 15, GPIO0); impl_pin!(P0_16, 0, 16, GPIO0); impl_pin!(P0_17, 0, 17, GPIO0); impl_pin!(P0_18, 0, 18, GPIO0); impl_pin!(P0_19, 0, 19, GPIO0); impl_pin!(P0_20, 0, 20, GPIO0); impl_pin!(P0_21, 0, 21, GPIO0); impl_pin!(P0_22, 0, 22, GPIO0); impl_pin!(P0_23, 0, 23, GPIO0); impl_pin!(P0_24, 0, 24, GPIO0); impl_pin!(P0_25, 0, 25, GPIO0); impl_pin!(P0_26, 0, 26, GPIO0); impl_pin!(P0_27, 0, 27, GPIO0); impl_pin!(P0_28, 0, 28, GPIO0); impl_pin!(P0_29, 0, 29, GPIO0); impl_pin!(P0_30, 0, 30, GPIO0); impl_pin!(P0_31, 0, 31, GPIO0); impl_pin!(P1_0, 1, 0, GPIO1); impl_pin!(P1_1, 1, 1, GPIO1); impl_pin!(P1_2, 1, 2, GPIO1); impl_pin!(P1_3, 1, 3, GPIO1); impl_pin!(P1_4, 1, 4, GPIO1); impl_pin!(P1_5, 1, 5, GPIO1); impl_pin!(P1_6, 1, 6, GPIO1); impl_pin!(P1_7, 1, 7, GPIO1); impl_pin!(P1_8, 1, 8, GPIO1); impl_pin!(P1_9, 1, 9, GPIO1); impl_pin!(P1_10, 1, 10, GPIO1); impl_pin!(P1_11, 1, 11, GPIO1); impl_pin!(P1_12, 1, 12, GPIO1); impl_pin!(P1_13, 1, 13, GPIO1); impl_pin!(P1_14, 1, 14, GPIO1); impl_pin!(P1_15, 1, 15, GPIO1); impl_pin!(P1_16, 1, 16, GPIO1); impl_pin!(P1_17, 1, 17, GPIO1); impl_pin!(P1_18, 1, 18, GPIO1); impl_pin!(P1_19, 1, 19, GPIO1); impl_pin!(P1_20, 1, 20, GPIO1); impl_pin!(P1_21, 1, 21, GPIO1); impl_pin!(P1_22, 1, 22, GPIO1); impl_pin!(P1_23, 1, 23, GPIO1); impl_pin!(P1_24, 1, 24, GPIO1); impl_pin!(P1_25, 1, 25, GPIO1); impl_pin!(P1_26, 1, 26, GPIO1); impl_pin!(P1_27, 1, 27, GPIO1); impl_pin!(P1_28, 1, 28, GPIO1); #[cfg(feature = "dangerous-reset-as-gpio")] impl_pin!(P1_29, 1, 29, GPIO1); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_30, 1, 30, GPIO1); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_31, 1, 31, GPIO1); impl_pin!(P2_0, 2, 0, GPIO2); impl_pin!(P2_1, 2, 1, GPIO2); impl_pin!(P2_2, 2, 2, GPIO2); impl_pin!(P2_3, 2, 3, GPIO2); impl_pin!(P2_4, 2, 4, GPIO2); impl_pin!(P2_5, 2, 5, GPIO2); impl_pin!(P2_6, 2, 6, GPIO2); impl_pin!(P2_7, 2, 7, GPIO2); impl_pin!(P2_8, 2, 8, GPIO2); impl_pin!(P2_9, 2, 9, GPIO2); impl_pin!(P2_10, 2, 10, GPIO2); impl_pin!(P2_11, 2, 11, GPIO2); impl_pin!(P2_12, 2, 12, GPIO2); impl_pin!(P2_13, 2, 13, GPIO2); impl_pin!(P2_14, 2, 14, GPIO2); impl_pin!(P2_15, 2, 15, GPIO2); impl_pin!(P2_16, 2, 16, GPIO2); impl_pin!(P2_17, 2, 17, GPIO2); impl_pin!(P2_18, 2, 18, GPIO2); impl_pin!(P2_19, 2, 19, GPIO2); impl_pin!(P2_20, 2, 20, GPIO2); impl_pin!(P2_21, 2, 21, GPIO2); impl_pin!(P2_22, 2, 22, GPIO2); impl_pin!(P2_23, 2, 23, GPIO2); impl_pin!(P2_24, 2, 24, GPIO2); impl_pin!(P2_25, 2, 25, GPIO2); impl_pin!(P2_26, 2, 26, GPIO2); // impl_pin!(P2_27, 2, 27, GPIO2); // impl_pin!(P2_28, 2, 28, GPIO2); // impl_pin!(P2_29, 2, 29, GPIO2); // impl_pin!(P2_30, 2, 30, GPIO2); // impl_pin!(P2_31, 2, 31, GPIO2); impl_pin!(P3_0, 3, 0, GPIO3); impl_pin!(P3_1, 3, 1, GPIO3); impl_pin!(P3_2, 3, 2, GPIO3); impl_pin!(P3_3, 3, 3, GPIO3); impl_pin!(P3_4, 3, 4, GPIO3); impl_pin!(P3_5, 3, 5, GPIO3); impl_pin!(P3_6, 3, 6, GPIO3); impl_pin!(P3_7, 3, 7, GPIO3); impl_pin!(P3_8, 3, 8, GPIO3); impl_pin!(P3_9, 3, 9, GPIO3); impl_pin!(P3_10, 3, 10, GPIO3); impl_pin!(P3_11, 3, 11, GPIO3); impl_pin!(P3_12, 3, 12, GPIO3); impl_pin!(P3_13, 3, 13, GPIO3); impl_pin!(P3_14, 3, 14, GPIO3); impl_pin!(P3_15, 3, 15, GPIO3); impl_pin!(P3_16, 3, 16, GPIO3); impl_pin!(P3_17, 3, 17, GPIO3); impl_pin!(P3_18, 3, 18, GPIO3); impl_pin!(P3_19, 3, 19, GPIO3); impl_pin!(P3_20, 3, 20, GPIO3); impl_pin!(P3_21, 3, 21, GPIO3); impl_pin!(P3_22, 3, 22, GPIO3); impl_pin!(P3_23, 3, 23, GPIO3); impl_pin!(P3_24, 3, 24, GPIO3); impl_pin!(P3_25, 3, 25, GPIO3); impl_pin!(P3_26, 3, 26, GPIO3); impl_pin!(P3_27, 3, 27, GPIO3); impl_pin!(P3_28, 3, 28, GPIO3); impl_pin!(P3_29, 3, 29, GPIO3); impl_pin!(P3_30, 3, 30, GPIO3); impl_pin!(P3_31, 3, 31, GPIO3); impl_pin!(P4_0, 4, 0, GPIO4); impl_pin!(P4_1, 4, 1, GPIO4); impl_pin!(P4_2, 4, 2, GPIO4); impl_pin!(P4_3, 4, 3, GPIO4); impl_pin!(P4_4, 4, 4, GPIO4); impl_pin!(P4_5, 4, 5, GPIO4); impl_pin!(P4_6, 4, 6, GPIO4); impl_pin!(P4_7, 4, 7, GPIO4); // impl_pin!(P4_8, 4, 8, GPIO4); // impl_pin!(P4_9, 4, 9, GPIO4); // impl_pin!(P4_10, 4, 10, GPIO4); // impl_pin!(P4_11, 4, 11, GPIO4); // impl_pin!(P4_12, 4, 12, GPIO4); // impl_pin!(P4_13, 4, 13, GPIO4); // impl_pin!(P4_14, 4, 14, GPIO4); // impl_pin!(P4_15, 4, 15, GPIO4); // impl_pin!(P4_16, 4, 16, GPIO4); // impl_pin!(P4_17, 4, 17, GPIO4); // impl_pin!(P4_18, 4, 18, GPIO4); // impl_pin!(P4_19, 4, 19, GPIO4); // impl_pin!(P4_20, 4, 20, GPIO4); // impl_pin!(P4_21, 4, 21, GPIO4); // impl_pin!(P4_22, 4, 22, GPIO4); // impl_pin!(P4_23, 4, 23, GPIO4); // impl_pin!(P4_24, 4, 24, GPIO4); // impl_pin!(P4_25, 4, 25, GPIO4); // impl_pin!(P4_26, 4, 26, GPIO4); // impl_pin!(P4_27, 4, 27, GPIO4); // impl_pin!(P4_28, 4, 28, GPIO4); // impl_pin!(P4_29, 4, 29, GPIO4); // impl_pin!(P4_30, 4, 30, GPIO4); // impl_pin!(P4_31, 4, 31, GPIO4); } /// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, /// for various low level peripherals. pub(crate) mod peripheral_gating { use paste::paste; use crate::clocks::Gate; use crate::clocks::periph_helpers::{ AdcConfig, CTimerConfig, Clk1MConfig, I3cConfig, Lpi2cConfig, LpspiConfig, LpuartConfig, NoConfig, OsTimerConfig, }; use crate::{impl_cc_gate, pac}; // These peripherals have no additional upstream clocks or configuration required // other than enabling through the MRCC gate. Currently, these peripherals will // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or // [`SPConfHelper::post_enable_config()`]. impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig); impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig); impl_cc_gate!(CRC0, mrcc_glb_cc0, mrcc_glb_rst0, crc0, NoConfig); impl_cc_gate!(INPUTMUX0, mrcc_glb_cc0, mrcc_glb_rst0, inputmux0, NoConfig); // These peripherals DO have meaningful configuration, and could fail if the system // clocks do not match their needs. impl_cc_gate!(ADC0, mrcc_glb_cc1, mrcc_glb_rst1, adc0, AdcConfig); impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); impl_cc_gate!(I3C0, mrcc_glb_cc0, mrcc_glb_rst0, i3c0, I3cConfig); impl_cc_gate!(CTIMER0, mrcc_glb_cc0, mrcc_glb_rst0, ctimer0, CTimerConfig); impl_cc_gate!(CTIMER1, mrcc_glb_cc0, mrcc_glb_rst0, ctimer1, CTimerConfig); impl_cc_gate!(CTIMER2, mrcc_glb_cc0, mrcc_glb_rst0, ctimer2, CTimerConfig); impl_cc_gate!(CTIMER3, mrcc_glb_cc0, mrcc_glb_rst0, ctimer3, CTimerConfig); impl_cc_gate!(CTIMER4, mrcc_glb_cc0, mrcc_glb_rst0, ctimer4, CTimerConfig); impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); // TRNG peripheral - uses NoConfig since it has no selectable clock source impl_cc_gate!(TRNG0, mrcc_glb_cc1, mrcc_glb_rst1, trng0, NoConfig); // Peripherals that use ACC instead of CC! impl_cc_gate!(LPUART0, mrcc_glb_acc0, mrcc_glb_rst0, lpuart0, LpuartConfig); impl_cc_gate!(LPUART1, mrcc_glb_acc0, mrcc_glb_rst0, lpuart1, LpuartConfig); impl_cc_gate!(LPUART2, mrcc_glb_acc0, mrcc_glb_rst0, lpuart2, LpuartConfig); impl_cc_gate!(LPUART3, mrcc_glb_acc0, mrcc_glb_rst0, lpuart3, LpuartConfig); impl_cc_gate!(LPUART4, mrcc_glb_acc0, mrcc_glb_rst0, lpuart4, LpuartConfig); // DMA0 peripheral - uses NoConfig since it has no selectable clock source impl_cc_gate!(DMA0, mrcc_glb_acc0, mrcc_glb_rst0, dma0, NoConfig); impl_cc_gate!(GPIO0, mrcc_glb_acc2, mrcc_glb_rst2, gpio0, NoConfig); impl_cc_gate!(GPIO1, mrcc_glb_acc2, mrcc_glb_rst2, gpio1, NoConfig); impl_cc_gate!(GPIO2, mrcc_glb_acc2, mrcc_glb_rst2, gpio2, NoConfig); impl_cc_gate!(GPIO3, mrcc_glb_acc2, mrcc_glb_rst2, gpio3, NoConfig); impl_cc_gate!(GPIO4, mrcc_glb_acc2, mrcc_glb_rst2, gpio4, NoConfig); impl_cc_gate!(LPI2C0, mrcc_glb_acc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig); impl_cc_gate!(LPI2C1, mrcc_glb_acc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig); impl_cc_gate!(LPI2C2, mrcc_glb_acc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig); impl_cc_gate!(LPI2C3, mrcc_glb_acc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig); impl_cc_gate!(LPSPI0, mrcc_glb_acc0, mrcc_glb_rst0, lpspi0, LpspiConfig); impl_cc_gate!(LPSPI1, mrcc_glb_acc0, mrcc_glb_rst0, lpspi1, LpspiConfig); impl_cc_gate!(WWDT0, mrcc_glb_acc0, wwdt0, Clk1MConfig); } pub(crate) mod clock_limits { use crate::chips::ClockLimits; // TODO: Different for different CPUs? pub const VDD_CORE_MID_DRIVE_WAIT_STATE_LIMITS: &[(u32, u8)] = &[(22_500_000, 0b0000)]; pub const VDD_CORE_MID_DRIVE_MAX_WAIT_STATES: u8 = 0b0001; pub const VDD_CORE_OVER_DRIVE_WAIT_STATE_LIMITS: &[(u32, u8)] = &[ (40_000_000, 0b0000), (80_000_000, 0b0001), (120_000_000, 0b0010), (160_000_000, 0b0011), ]; pub const VDD_CORE_OVER_DRIVE_MAX_WAIT_STATES: u8 = 0b0100; impl ClockLimits { pub const MID_DRIVE: Self = Self { fro_hf: 90_000_000, fro_hf_div: 45_000_000, pll1_clk: 48_000_000, main_clk: 90_000_000, cpu_clk: 45_000_000, pll1_clk_div: 48_000_000, // clk_16k: 16_384, // clk_in: 50_000_000, // clk_48m: 48_000_000, // fro_12m: 24_000_000, // what? // fro_12m_div: 24_000_000, // what? // clk_1m: 1_000_000, // system_clk: 45_000_000, // bus_clk: 22_500_000, // slow_clk: 7_500_000, }; pub const OVER_DRIVE: Self = Self { fro_hf: 180_000_000, fro_hf_div: 180_000_000, pll1_clk: 240_000_000, main_clk: 180_000_000, cpu_clk: 180_000_000, pll1_clk_div: 240_000_000, // clk_16k: 16_384, // clk_in: 50_000_000, // clk_48m: 48_000_000, // fro_12m: 24_000_000, // what? // fro_12m_div: 24_000_000, // what? // clk_1m: 1_000_000, // system_clk: 180_000_000, // bus_clk: 90_000_000, // slow_clk: 36_000_000, }; } } ================================================ FILE: embassy-mcxa/src/chips/mcxa5xx.rs ================================================ //! Module for MCXA5xx family pub use inner_periph::{Peripherals, peripherals}; use crate::interrupt::InterruptExt; // NOTE: macro generates missing safety docs and unsafe calls in unsafe blocks, // allow for now, and put in a module so we can apply the rule to that scope. #[allow(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] mod inner_periph { #[rustfmt::skip] embassy_hal_internal::peripherals!( ADC0, ADC1, // AOI0, // AOI1, // CAN0, // CAN1, CDOG0, CDOG1, // CLKOUT is not specifically a peripheral (it's part of SYSCON), // but we still want it to be a singleton. CLKOUT, // CMC, // CMP0, // CMP1, CRC0, CTIMER0, CTIMER0_CH0, CTIMER0_CH1, CTIMER0_CH2, CTIMER0_CH3, CTIMER1, CTIMER1_CH0, CTIMER1_CH1, CTIMER1_CH2, CTIMER1_CH3, CTIMER2, CTIMER2_CH0, CTIMER2_CH1, CTIMER2_CH2, CTIMER2_CH3, CTIMER3, CTIMER3_CH0, CTIMER3_CH1, CTIMER3_CH2, CTIMER3_CH3, CTIMER4, CTIMER4_CH0, CTIMER4_CH1, CTIMER4_CH2, CTIMER4_CH3, // DBGMAILBOX, DMA0, DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3, DMA_CH4, DMA_CH5, DMA_CH6, DMA_CH7, // Need more work on the DMA driver before we can enable these // DMA_CH8, // DMA_CH9, // DMA_CH10, // DMA_CH11, EDMA0_TCD0, // Need more work on the DMA driver before we can enable this // EDMA0_TCD1, // EIM0, // EQDC0, // EQDC1, // ERM0, // FLEXIO0, // FLEXPWM0, // FLEXPWM1, // FMC0, // FMU0, // FREQME0, // GLIKEY0, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, I3C0, I3C1, I3C2, I3C3, INPUTMUX0, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPSPI0, LPSPI1, LPSPI2, LPSPI3, LPSPI4, LPSPI5, // LPTMR0, LPUART0, LPUART1, LPUART2, LPUART3, LPUART4, LPUART5, // MAU0, // MBC0, // MRCC0, // OPAMP0, OSTIMER0, // Normally SWDIO! #[cfg(feature = "swd-as-gpio")] P0_0, // Normally SWCLK! #[cfg(feature = "swd-as-gpio")] P0_1, // Normally SWO! #[cfg(feature = "swd-swo-as-gpio")] P0_2, // Normally JTAG TDI! #[cfg(feature = "jtag-extras-as-gpio")] P0_3, P0_4, P0_5, // Normally JTAG ISPMODE_N! #[cfg(feature = "jtag-extras-as-gpio")] P0_6, P0_7, P0_8, P0_9, 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, P1_0, P1_1, P1_2, P1_3, P1_4, P1_5, P1_6, P1_7, P1_8, P1_9, P1_10, P1_11, P1_12, P1_13, P1_14, P1_15, P1_16, P1_17, P1_18, P1_19, // Normally RESET_B! #[cfg(feature = "dangerous-reset-as-gpio")] P1_29, // Normally XTAL48M! #[cfg(feature = "sosc-as-gpio")] P1_30, // Normally EXTAL48M! #[cfg(feature = "sosc-as-gpio")] P1_31, P2_0, P2_1, P2_2, P2_3, P2_4, P2_5, P2_6, P2_7, P2_8, P2_9, P2_10, P2_11, P2_12, P2_13, P2_14, P2_15, P2_16, P2_17, P2_18, P2_19, P2_20, P2_21, P2_22, P2_23, P2_24, P2_25, P2_26, P2_28, P2_29, P2_30, P2_31, P3_0, P3_1, P3_2, P3_3, P3_4, P3_5, P3_6, P3_7, P3_8, P3_9, P3_10, P3_11, P3_12, P3_13, P3_14, P3_15, P3_16, P3_17, P3_18, P3_19, P3_20, P3_21, P3_22, P3_23, P3_24, P3_25, P3_26, P3_27, P3_28, P3_29, P3_30, P3_31, P4_0, P4_1, P4_2, P4_3, P4_4, P4_5, P4_6, P4_7, P4_8, P4_9, P4_10, P4_11, P4_12, P4_13, // Normally EXTAL32K! #[cfg(feature = "rosc-32k-as-gpio")] P5_0, // Normally XTAL32K! #[cfg(feature = "rosc-32k-as-gpio")] P5_1, P5_2, P5_3, P5_4, P5_5, P5_6, P5_7, P5_8, P5_9, // PKC0, PORT0, PORT1, PORT2, PORT3, PORT4, PORT5, RTC0, // SAU, // SCG0, // SCN_SCB, // SGI0, // SMARTDMA0, // SPC0, // SYSCON, // TDET0, TRNG0, // UDF0, // USB0, // UTICK0, // VBAT0, // WAKETIMER0, // WUU0, WWDT0, WWDT1, ); } // NOTE: Macro has missing safety docs and makes unsafe calls in unsafe fns pub use inner_interrupt::*; #[allow(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] mod inner_interrupt { #[rustfmt::skip] embassy_hal_internal::interrupt_mod!( ADC0, ADC1, // CAN0, // CAN1, CDOG0, CDOG1, // CMC, // CMP0, // CMP1, // CMP2, CTIMER0, CTIMER1, CTIMER2, CTIMER3, CTIMER4, // DAC0, DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3, DMA_CH4, DMA_CH5, DMA_CH6, DMA_CH7, DMA_CH8, DMA_CH9, DMA_CH10, DMA_CH11, // EQDC0_COMPARE, // EQDC0_HOME, // EQDC0_INDEX, // EQDC0_WATCHDOG, // EQDC1_COMPARE, // EQDC1_HOME, // EQDC1_INDEX, // EQDC1_WATCHDOG, // ERM0_MULTI_BIT, // ERM0_SINGLE_BIT, // FLEXIO, // FLEXPWM0_FAULT, // FLEXPWM0_RELOAD_ERROR, // FLEXPWM0_SUBMODULE0, // FLEXPWM0_SUBMODULE1, // FLEXPWM0_SUBMODULE2, // FLEXPWM0_SUBMODULE3, // FLEXPWM1_FAULT, // FLEXPWM1_RELOAD_ERROR, // FLEXPWM1_SUBMODULE0, // FLEXPWM1_SUBMODULE1, // FLEXPWM1_SUBMODULE2, // FLEXPWM1_SUBMODULE3, // FMU0, // FREQME0, // GLIKEY0, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, I3C0, I3C1, I3C2, I3C3, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPSPI0, LPSPI1, LPSPI2, LPSPI3, LPSPI4, LPSPI5, // LPTMR0, LPUART0, LPUART1, LPUART2, LPUART3, LPUART4, LPUART5, // MAU, // MBC0, OS_EVENT, // PKC, RTC, // SCG0, // SGI, // SLCD, // SMARTDMA, // SPC0, // TDET, TRNG0, // USB0, // UTICK0, // WAKETIMER0, // WUU0, WWDT0, WWDT1, ); } /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). pub fn init(cfg: crate::config::Config) -> Peripherals { // Might not need to be mutable if none of the `...-as-gpio` features are active. #[allow(unused_mut)] let mut peripherals = Peripherals::take(); // crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); crate::interrupt::GPIO0.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO1.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO2.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO3.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO4.set_priority(cfg.gpio_interrupt_priority); crate::interrupt::GPIO5.set_priority(cfg.gpio_interrupt_priority); // Configure clocks crate::clocks::init(cfg.clock_cfg).unwrap(); // Initialize embassy-time global driver backed by OSTIMER0 // NOTE: As early as possible, but MUST be AFTER clocks! crate::ostimer::init(cfg.time_interrupt_priority); // Initialize the INPUTMUX peripheral crate::inputmux::init(); // Enable interrupts unsafe { cortex_m::interrupt::enable(); } // Initialize DMA controller (clock, reset, configuration) crate::dma::init(); // Enable GPIO clocks unsafe { _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); } // import may be unused if none of the following features are set #[allow(unused_imports)] use crate::gpio::SealedPin; // If we are not using pins for specialized purposes, set them as disabled #[cfg(feature = "rosc-32k-as-gpio")] { peripherals.P5_0.set_as_disabled(); peripherals.P5_1.set_as_disabled(); } #[cfg(feature = "sosc-as-gpio")] { peripherals.P1_30.set_as_disabled(); peripherals.P1_31.set_as_disabled(); } #[cfg(feature = "swd-as-gpio")] { peripherals.P0_0.set_as_disabled(); peripherals.P0_1.set_as_disabled(); } #[cfg(feature = "swd-swo-as-gpio")] { peripherals.P0_2.set_as_disabled(); } #[cfg(feature = "jtag-extras-as-gpio")] { peripherals.P0_3.set_as_disabled(); peripherals.P0_6.set_as_disabled(); } #[cfg(feature = "dangerous-reset-as-gpio")] { // DANGER DANGER DANGER peripherals.P1_29.set_as_disabled(); } peripherals } // Chip specific GPIO impls mod gpio_impls { use crate::gpio::{AnyPin, GpioPin, Pull, SealedPin}; use crate::impl_pin; use crate::pac::common::{RW, Reg}; use crate::pac::gpio::vals::{Pdd, Pid}; use crate::pac::port::regs::Pcr; use crate::pac::port::vals::{Dse, Ibe, Mux, Sre}; #[cfg(feature = "swd-as-gpio")] impl_pin!(P0_0, 0, 0, GPIO0); #[cfg(feature = "swd-as-gpio")] impl_pin!(P0_1, 0, 1, GPIO0); #[cfg(feature = "swd-swo-as-gpio")] impl_pin!(P0_2, 0, 2, GPIO0); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_3, 0, 3, GPIO0); impl_pin!(P0_4, 0, 4, GPIO0); impl_pin!(P0_5, 0, 5, GPIO0); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_6, 0, 6, GPIO0); impl_pin!(P0_7, 0, 7, GPIO0); impl_pin!(P0_8, 0, 8, GPIO0); impl_pin!(P0_9, 0, 9, GPIO0); impl_pin!(P0_10, 0, 10, GPIO0); impl_pin!(P0_11, 0, 11, GPIO0); impl_pin!(P0_12, 0, 12, GPIO0); impl_pin!(P0_13, 0, 13, GPIO0); impl_pin!(P0_14, 0, 14, GPIO0); impl_pin!(P0_15, 0, 15, GPIO0); impl_pin!(P0_16, 0, 16, GPIO0); impl_pin!(P0_17, 0, 17, GPIO0); impl_pin!(P0_18, 0, 18, GPIO0); impl_pin!(P0_19, 0, 19, GPIO0); impl_pin!(P0_20, 0, 20, GPIO0); impl_pin!(P0_21, 0, 21, GPIO0); impl_pin!(P0_22, 0, 22, GPIO0); impl_pin!(P0_23, 0, 23, GPIO0); impl_pin!(P0_24, 0, 24, GPIO0); impl_pin!(P0_25, 0, 25, GPIO0); impl_pin!(P0_26, 0, 26, GPIO0); impl_pin!(P0_27, 0, 27, GPIO0); impl_pin!(P1_0, 1, 0, GPIO1); impl_pin!(P1_1, 1, 1, GPIO1); impl_pin!(P1_2, 1, 2, GPIO1); impl_pin!(P1_3, 1, 3, GPIO1); impl_pin!(P1_4, 1, 4, GPIO1); impl_pin!(P1_5, 1, 5, GPIO1); impl_pin!(P1_6, 1, 6, GPIO1); impl_pin!(P1_7, 1, 7, GPIO1); impl_pin!(P1_8, 1, 8, GPIO1); impl_pin!(P1_9, 1, 9, GPIO1); impl_pin!(P1_10, 1, 10, GPIO1); impl_pin!(P1_11, 1, 11, GPIO1); impl_pin!(P1_12, 1, 12, GPIO1); impl_pin!(P1_13, 1, 13, GPIO1); impl_pin!(P1_14, 1, 14, GPIO1); impl_pin!(P1_15, 1, 15, GPIO1); impl_pin!(P1_16, 1, 16, GPIO1); impl_pin!(P1_17, 1, 17, GPIO1); impl_pin!(P1_18, 1, 18, GPIO1); impl_pin!(P1_19, 1, 19, GPIO1); #[cfg(feature = "dangerous-reset-as-gpio")] impl_pin!(P1_29, 1, 29, GPIO1); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_30, 1, 30, GPIO1); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_31, 1, 31, GPIO1); impl_pin!(P2_0, 2, 0, GPIO2); impl_pin!(P2_1, 2, 1, GPIO2); impl_pin!(P2_2, 2, 2, GPIO2); impl_pin!(P2_3, 2, 3, GPIO2); impl_pin!(P2_4, 2, 4, GPIO2); impl_pin!(P2_5, 2, 5, GPIO2); impl_pin!(P2_6, 2, 6, GPIO2); impl_pin!(P2_7, 2, 7, GPIO2); impl_pin!(P2_8, 2, 8, GPIO2); impl_pin!(P2_9, 2, 9, GPIO2); impl_pin!(P2_10, 2, 10, GPIO2); impl_pin!(P2_11, 2, 11, GPIO2); impl_pin!(P2_12, 2, 12, GPIO2); impl_pin!(P2_13, 2, 13, GPIO2); impl_pin!(P2_14, 2, 14, GPIO2); impl_pin!(P2_15, 2, 15, GPIO2); impl_pin!(P2_16, 2, 16, GPIO2); impl_pin!(P2_17, 2, 17, GPIO2); impl_pin!(P2_18, 2, 18, GPIO2); impl_pin!(P2_19, 2, 19, GPIO2); impl_pin!(P2_20, 2, 20, GPIO2); impl_pin!(P2_21, 2, 21, GPIO2); impl_pin!(P2_22, 2, 22, GPIO2); impl_pin!(P2_23, 2, 23, GPIO2); impl_pin!(P2_24, 2, 24, GPIO2); impl_pin!(P2_25, 2, 25, GPIO2); impl_pin!(P2_26, 2, 26, GPIO2); impl_pin!(P2_28, 2, 28, GPIO2); impl_pin!(P2_29, 2, 29, GPIO2); impl_pin!(P2_30, 2, 30, GPIO2); impl_pin!(P2_31, 2, 31, GPIO2); impl_pin!(P3_0, 3, 0, GPIO3); impl_pin!(P3_1, 3, 1, GPIO3); impl_pin!(P3_2, 3, 2, GPIO3); impl_pin!(P3_3, 3, 3, GPIO3); impl_pin!(P3_4, 3, 4, GPIO3); impl_pin!(P3_5, 3, 5, GPIO3); impl_pin!(P3_6, 3, 6, GPIO3); impl_pin!(P3_7, 3, 7, GPIO3); impl_pin!(P3_8, 3, 8, GPIO3); impl_pin!(P3_9, 3, 9, GPIO3); impl_pin!(P3_10, 3, 10, GPIO3); impl_pin!(P3_11, 3, 11, GPIO3); impl_pin!(P3_12, 3, 12, GPIO3); impl_pin!(P3_13, 3, 13, GPIO3); impl_pin!(P3_14, 3, 14, GPIO3); impl_pin!(P3_15, 3, 15, GPIO3); impl_pin!(P3_16, 3, 16, GPIO3); impl_pin!(P3_17, 3, 17, GPIO3); impl_pin!(P3_18, 3, 18, GPIO3); impl_pin!(P3_19, 3, 19, GPIO3); impl_pin!(P3_20, 3, 20, GPIO3); impl_pin!(P3_21, 3, 21, GPIO3); impl_pin!(P3_22, 3, 22, GPIO3); impl_pin!(P3_23, 3, 23, GPIO3); impl_pin!(P3_24, 3, 24, GPIO3); impl_pin!(P3_25, 3, 25, GPIO3); impl_pin!(P3_26, 3, 26, GPIO3); impl_pin!(P3_27, 3, 27, GPIO3); impl_pin!(P3_28, 3, 28, GPIO3); impl_pin!(P3_29, 3, 29, GPIO3); impl_pin!(P3_30, 3, 30, GPIO3); impl_pin!(P3_31, 3, 31, GPIO3); impl_pin!(P4_0, 4, 0, GPIO4); impl_pin!(P4_1, 4, 1, GPIO4); impl_pin!(P4_2, 4, 2, GPIO4); impl_pin!(P4_3, 4, 3, GPIO4); impl_pin!(P4_4, 4, 4, GPIO4); impl_pin!(P4_5, 4, 5, GPIO4); impl_pin!(P4_6, 4, 6, GPIO4); impl_pin!(P4_7, 4, 7, GPIO4); impl_pin!(P4_8, 4, 8, GPIO4); impl_pin!(P4_9, 4, 9, GPIO4); impl_pin!(P4_10, 4, 10, GPIO4); impl_pin!(P4_11, 4, 11, GPIO4); impl_pin!(P4_12, 4, 12, GPIO4); impl_pin!(P4_13, 4, 13, GPIO4); #[cfg(feature = "rosc-32k-as-gpio")] impl_pin!(P5_0, 5, 0, GPIO5); #[cfg(feature = "rosc-32k-as-gpio")] impl_pin!(P5_1, 5, 1, GPIO5); impl_pin!(P5_2, 5, 2, GPIO5); impl_pin!(P5_3, 5, 3, GPIO5); impl_pin!(P5_4, 5, 4, GPIO5); impl_pin!(P5_5, 5, 5, GPIO5); impl_pin!(P5_6, 5, 6, GPIO5); impl_pin!(P5_7, 5, 7, GPIO5); impl_pin!(P5_8, 5, 8, GPIO5); impl_pin!(P5_9, 5, 9, GPIO5); } /// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, /// for various low level peripherals. pub(crate) mod peripheral_gating { use paste::paste; use crate::clocks::Gate; use crate::clocks::periph_helpers::{ AdcConfig, CTimerConfig, Clk1MConfig, I3cConfig, Lpi2cConfig, LpspiConfig, LpuartConfig, NoConfig, OsTimerConfig, }; use crate::{impl_cc_gate, pac}; // These peripherals have no additional upstream clocks or configuration required // other than enabling through the MRCC gate. Currently, these peripherals will // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or // [`SPConfHelper::post_enable_config()`]. impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig); impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig); impl_cc_gate!(PORT5, mrcc_glb_cc1, port5, NoConfig); impl_cc_gate!(CRC0, mrcc_glb_cc0, mrcc_glb_rst0, crc0, NoConfig); impl_cc_gate!(INPUTMUX0, mrcc_glb_cc0, mrcc_glb_rst0, inputmux0, NoConfig); // These peripherals DO have meaningful configuration, and could fail if the system // clocks do not match their needs. impl_cc_gate!(ADC0, mrcc_glb_cc1, mrcc_glb_rst1, adc0, AdcConfig); impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); impl_cc_gate!(I3C0, mrcc_glb_acc2, mrcc_glb_rst2, i3c0, I3cConfig); impl_cc_gate!(I3C1, mrcc_glb_acc2, mrcc_glb_rst2, i3c1, I3cConfig); impl_cc_gate!(I3C2, mrcc_glb_acc2, mrcc_glb_rst2, i3c2, I3cConfig); impl_cc_gate!(I3C3, mrcc_glb_acc2, mrcc_glb_rst2, i3c3, I3cConfig); impl_cc_gate!(CTIMER0, mrcc_glb_acc0, mrcc_glb_rst0, ctimer0, CTimerConfig); impl_cc_gate!(CTIMER1, mrcc_glb_acc0, mrcc_glb_rst0, ctimer1, CTimerConfig); impl_cc_gate!(CTIMER2, mrcc_glb_acc0, mrcc_glb_rst0, ctimer2, CTimerConfig); impl_cc_gate!(CTIMER3, mrcc_glb_acc0, mrcc_glb_rst0, ctimer3, CTimerConfig); impl_cc_gate!(CTIMER4, mrcc_glb_acc0, mrcc_glb_rst0, ctimer4, CTimerConfig); impl_cc_gate!(OSTIMER0, mrcc_glb_cc0, mrcc_glb_rst0, ostimer0, OsTimerConfig); // TRNG peripheral - uses NoConfig since it has no selectable clock source impl_cc_gate!(TRNG0, mrcc_glb_acc4, mrcc_glb_rst4, trng0, NoConfig); // Peripherals that use ACC instead of CC! impl_cc_gate!(LPUART0, mrcc_glb_acc0, mrcc_glb_rst0, lpuart0, LpuartConfig); impl_cc_gate!(LPUART1, mrcc_glb_acc0, mrcc_glb_rst0, lpuart1, LpuartConfig); impl_cc_gate!(LPUART2, mrcc_glb_acc0, mrcc_glb_rst0, lpuart2, LpuartConfig); impl_cc_gate!(LPUART3, mrcc_glb_acc0, mrcc_glb_rst0, lpuart3, LpuartConfig); impl_cc_gate!(LPUART4, mrcc_glb_acc0, mrcc_glb_rst0, lpuart4, LpuartConfig); impl_cc_gate!(LPUART5, mrcc_glb_acc0, mrcc_glb_rst0, lpuart5, LpuartConfig); // DMA0 peripheral - uses NoConfig since it has no selectable clock source impl_cc_gate!(DMA0, mrcc_glb_acc0, mrcc_glb_rst0, dma0, NoConfig); impl_cc_gate!(GPIO0, mrcc_glb_acc3, mrcc_glb_rst3, gpio0, NoConfig); impl_cc_gate!(GPIO1, mrcc_glb_acc3, mrcc_glb_rst3, gpio1, NoConfig); impl_cc_gate!(GPIO2, mrcc_glb_acc3, mrcc_glb_rst3, gpio2, NoConfig); impl_cc_gate!(GPIO3, mrcc_glb_acc3, mrcc_glb_rst3, gpio3, NoConfig); impl_cc_gate!(GPIO4, mrcc_glb_acc3, mrcc_glb_rst3, gpio4, NoConfig); impl_cc_gate!(GPIO5, mrcc_glb_cc3, gpio5, NoConfig); impl_cc_gate!(LPI2C0, mrcc_glb_acc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig); impl_cc_gate!(LPI2C1, mrcc_glb_acc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig); impl_cc_gate!(LPI2C2, mrcc_glb_acc0, mrcc_glb_rst0, lpi2c2, Lpi2cConfig); impl_cc_gate!(LPI2C3, mrcc_glb_acc0, mrcc_glb_rst0, lpi2c3, Lpi2cConfig); impl_cc_gate!(LPSPI0, mrcc_glb_acc1, mrcc_glb_rst1, lpspi0, LpspiConfig); impl_cc_gate!(LPSPI1, mrcc_glb_acc1, mrcc_glb_rst1, lpspi1, LpspiConfig); impl_cc_gate!(LPSPI2, mrcc_glb_acc1, mrcc_glb_rst1, lpspi2, LpspiConfig); impl_cc_gate!(LPSPI3, mrcc_glb_acc1, mrcc_glb_rst1, lpspi3, LpspiConfig); impl_cc_gate!(LPSPI4, mrcc_glb_acc1, mrcc_glb_rst1, lpspi4, LpspiConfig); impl_cc_gate!(LPSPI5, mrcc_glb_acc1, mrcc_glb_rst1, lpspi5, LpspiConfig); impl_cc_gate!(WWDT0, mrcc_glb_acc0, wwdt0, Clk1MConfig); impl_cc_gate!(WWDT1, mrcc_glb_acc0, wwdt1, Clk1MConfig); } pub(crate) mod clock_limits { #![allow(dead_code)] use crate::chips::ClockLimits; pub const VDD_CORE_MID_DRIVE_WAIT_STATE_LIMITS: &[(u32, u8)] = &[(24_000_000, 0b0000)]; // <= 48MHz pub const VDD_CORE_MID_DRIVE_MAX_WAIT_STATES: u8 = 0b0001; pub const VDD_CORE_NORMAL_DRIVE_WAIT_STATE_LIMITS: &[(u32, u8)] = &[(30_000_000, 0b0000), (60_000_000, 0b0001), (90_000_000, 0b0010)]; // <= 120MHz pub const VDD_CORE_NORMAL_DRIVE_MAX_WAIT_STATES: u8 = 0b0011; pub const VDD_CORE_OVER_DRIVE_WAIT_STATE_LIMITS: &[(u32, u8)] = &[ (40_000_000, 0b0000), (80_000_000, 0b0001), (120_000_000, 0b0010), (160_000_000, 0b0011), (200_000_000, 0b0100), ]; // <= 250MHz pub const VDD_CORE_OVER_DRIVE_MAX_WAIT_STATES: u8 = 0b0101; impl ClockLimits { pub const MID_DRIVE: Self = Self { fro_hf: 96_000_000, fro_hf_div: 48_000_000, pll1_clk: 100_000_000, pll1_clk_div: 100_000_000, main_clk: 96_000_000, cpu_clk: 48_000_000, // clk_16k: 16_384, // clk_in: 50_000_000, // clk_48m: 48_000_000, // fro_12m: 12_000_000, // fro_12m_div: 12_000_000, // clk_1m: 1_000_000, // system_clk: cpu_clk, // bus_clk: cpu_clk / 2, // slow_clk: cpu_clk / 6, }; pub const NORMAL_DRIVE: Self = Self { fro_hf: 192_000_000, fro_hf_div: 192_000_000, pll1_clk: 300_000_000, pll1_clk_div: 150_000_000, main_clk: 120_000_000, cpu_clk: 120_000_000, // clk_16k: 16_384, // clk_in: 50_000_000, // clk_48m: 48_000_000, // fro_12m: 12_000_000, // fro_12m_div: 12_000_000, // clk_1m: 1_000_000, // system_clk: cpu_clk, // bus_clk: cpu_clk / 2, // slow_clk: cpu_clk / 6, }; pub const OVER_DRIVE: Self = Self { fro_hf: 192_000_000, fro_hf_div: 192_000_000, pll1_clk: 400_000_000, pll1_clk_div: 200_000_000, main_clk: 240_000_000, cpu_clk: 240_000_000, // clk_16k: 16_384, // clk_in: 50_000_000, // clk_48m: 48_000_000, // fro_12m: 12_000_000, // fro_12m_div: 12_000_000, // clk_1m: 1_000_000, // system_clk: cpu_clk, // bus_clk: cpu_clk / 2, // slow_clk: cpu_clk / 6, }; } } ================================================ FILE: embassy-mcxa/src/chips/mod.rs ================================================ #[cfg(feature = "mcxa2xx")] pub mod mcxa2xx; #[cfg(feature = "mcxa5xx")] pub mod mcxa5xx; #[cfg(feature = "mcxa2xx")] pub(crate) use mcxa2xx::clock_limits; #[cfg(feature = "mcxa5xx")] pub(crate) use mcxa5xx::clock_limits; // From Table 165 - Max Clock Frequencies (mcxa2xx) // From Table 375 - Max. Clock Frequency (mcxa5xx) #[allow(dead_code)] pub(crate) struct ClockLimits { pub(crate) fro_hf: u32, pub(crate) fro_hf_div: u32, pub(crate) pll1_clk: u32, pub(crate) main_clk: u32, pub(crate) cpu_clk: u32, pub(crate) pll1_clk_div: u32, // The following items are LISTED in Table 165, but are not necessary // to check at runtime either because they are physically fixed, the // HAL exposes no way for them to exceed their limits, or they cannot // exceed their limits due to some upstream clock enforcement. They // are included here as documentation. // // clk_16k: u32, // fixed (16.384kHz), no need to check // clk_in: u32, // Checked already in configure_sosc method, 50MHz in all modes // clk_48m: u32, // clk_48m is fixed (to 45mhz actually) // fro_12m: u32, // We don't allow modifying from 12mhz // fro_12m_div: u32, // div can never exceed 12mhz // clk_1m: u32, // fro_12m / 12 can never exceed 12mhz // system_clk: u32, // cpu_clk == system_clk // bus_clk: u32, // bus_clk == (cpu_clk / 2), if cpu_clk is good so is bus_clk // slow_clk: u32, // slow_clk == (cpu_clk / 6), if cpu_clk is good so is slow_clock } ================================================ FILE: embassy-mcxa/src/clkout.rs ================================================ //! CLKOUT pseudo-peripheral //! //! CLKOUT is a part of the clock generation subsystem, and can be used //! either to generate arbitrary waveforms, or to debug the state of //! internal oscillators. use core::marker::PhantomData; use embassy_hal_internal::Peri; use crate::clocks::config::VddLevel; pub use crate::clocks::periph_helpers::Div4; use crate::clocks::{ClockError, PoweredClock, WakeGuard, with_clocks}; use crate::gpio::{AnyPin, SealedPin}; use crate::pac::mrcc::vals::{ClkdivHalt, ClkdivReset, ClkdivUnstab, ClkoutClkselMux as Mux}; use crate::peripherals::CLKOUT; /// A peripheral representing the CLKOUT pseudo-peripheral pub struct ClockOut<'a> { _p: PhantomData<&'a mut CLKOUT>, pin: Peri<'a, AnyPin>, freq: u32, _wg: Option, } /// Selected clock source to output #[derive(Copy, Clone)] pub enum ClockOutSel { /// 12MHz Internal Oscillator Fro12M, /// FRO180M/FRO192M Internal Oscillator, via divisor FroHfDiv, /// External Oscillator #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, /// 16KHz oscillator #[cfg(feature = "mcxa2xx")] Clk16K, /// Either the 16K or 32K oscillator, depending on settings #[cfg(feature = "mcxa5xx")] LpOsc, /// Output of PLL1 #[cfg(feature = "mcxa2xx")] Pll1Clk, /// Output of divided PLL1 #[cfg(feature = "mcxa5xx")] Pll1ClkDiv, /// Main System CPU clock, divided by 6 SlowClk, } /// Configuration for the ClockOut #[derive(Copy, Clone)] pub struct Config { /// Selected Source Clock pub sel: ClockOutSel, /// Selected division level pub div: Div4, /// Selected power level pub level: PoweredClock, } impl<'a> ClockOut<'a> { /// Create a new ClockOut pin. On success, the clock signal will begin immediately /// on the given pin. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new( _peri: Peri<'a, CLKOUT>, pin: Peri<'a, impl sealed::ClockOutPin>, cfg: Config, ) -> Result { // There's no MRCC enable bit, so we check the validity of the clocks here let (freq, mux, _wg) = check_sel(cfg.sel, cfg.level, cfg.div.into_divisor())?; // All good! Apply requested config, starting with the pin. pin.mux(); setup_clkout(mux, cfg.div); Ok(Self { _p: PhantomData, pin: pin.into(), freq: freq / cfg.div.into_divisor(), _wg, }) } /// Unsafe constructor that ignores PoweredClock checks and discards WakeGuards /// /// Only intended for debugging low power clock gating, to ensure that clocks start/stop /// appropriately. /// /// ## SAFETY /// /// The caller must not rely on the clock running for correctness if the provided /// clock will be gated in deep sleep mode. pub unsafe fn new_unchecked( _peri: Peri<'a, CLKOUT>, pin: Peri<'a, impl sealed::ClockOutPin>, mut cfg: Config, ) -> Result { // Ignore the users clock selection so it Just Works cfg.level = PoweredClock::NormalEnabledDeepSleepDisabled; // There's no MRCC enable bit, so we check the validity of the clocks here let (freq, mux, _wg) = check_sel(cfg.sel, cfg.level, cfg.div.into_divisor())?; // All good! Apply requested config, starting with the pin. pin.mux(); setup_clkout(mux, cfg.div); Ok(Self { _p: PhantomData, pin: pin.into(), freq: freq / cfg.div.into_divisor(), // No wake guards here! _wg: None, }) } /// Frequency of the clkout pin #[inline] pub fn frequency(&self) -> u32 { self.freq } } impl Drop for ClockOut<'_> { fn drop(&mut self) { disable_clkout(); self.pin.set_as_disabled(); } } /// Check whether the given clock selection is valid fn check_sel(sel: ClockOutSel, level: PoweredClock, divisor: u32) -> Result<(u32, Mux, Option), ClockError> { let res = with_clocks(|c| { #[cfg(feature = "mcxa2xx")] let (freq, mux, fmax, expected) = { let (freq, mux) = match sel { ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::CLKROOT_12M), ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::CLKROOT_FIRC_DIV), #[cfg(not(feature = "sosc-as-gpio"))] ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::CLKROOT_SOSC), ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::CLKROOT_16K), ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::CLKROOT_SPLL), ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::CLKROOT_SLOW), }; let expected = freq / divisor; let fmax = match c.active_power { VddLevel::MidDriveMode => 45_000_000, VddLevel::OverDriveMode => 90_000_000, }; (freq, mux, fmax, expected) }; #[cfg(feature = "mcxa5xx")] let (freq, mux, fmax, expected) = { let (freq, mux) = match sel { ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::I0_CLKROOT_12M), ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::I1_CLKROOT_FIRC_DIV), #[cfg(not(feature = "sosc-as-gpio"))] ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::I2_CLKROOT_SOSC), // TODO: we need this to be an lp_osc clock ClockOutSel::LpOsc => (c.ensure_clk_32k_vdd_core_active(&level)?, Mux::I3_CLKROOT_LPOSC), ClockOutSel::Pll1ClkDiv => (c.ensure_pll1_clk_div_active(&level)?, Mux::I5_CLKROOT_SPLL_DIV), ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::I6_CLKROOT_SLOW), }; let expected = freq / divisor; let fmax = match c.active_power { VddLevel::MidDriveMode => 48_000_000, VddLevel::NormalMode => 100_000_000, VddLevel::OverDriveMode => 100_000_000, }; (freq, mux, fmax, expected) }; if expected > fmax { Err(ClockError::BadConfig { clock: "clkout fclk", reason: "exceeds fclk max", }) } else { let wg = WakeGuard::for_power(&level); Ok((freq, mux, wg)) } }); let Some(res) = res else { return Err(ClockError::NeverInitialized); }; res } /// Set up the clkout pin using the given mux and div settings fn setup_clkout(mux: Mux, div: Div4) { let mrcc = crate::pac::MRCC0; mrcc.mrcc_clkout_clksel().write(|w| w.set_mux(mux)); // Set up clkdiv mrcc.mrcc_clkout_clkdiv().write(|w| { w.set_halt(ClkdivHalt::OFF); w.set_reset(ClkdivReset::OFF); w.set_div(div.into_bits()); }); mrcc.mrcc_clkout_clkdiv().write(|w| { w.set_halt(ClkdivHalt::ON); w.set_reset(ClkdivReset::ON); w.set_div(div.into_bits()); }); while mrcc.mrcc_clkout_clkdiv().read().unstab() == ClkdivUnstab::ON {} } /// Stop the fn disable_clkout() { // Stop the output by selecting the "none" clock // // TODO: restore the pin to hi-z or something? let mrcc = crate::pac::MRCC0; mrcc.mrcc_clkout_clkdiv().write(|w| { w.set_reset(ClkdivReset::OFF); w.set_halt(ClkdivHalt::OFF); w.set_div(0); }); mrcc.mrcc_clkout_clksel().write(|w| w.0 = 0b111); } mod sealed { use embassy_hal_internal::PeripheralType; use crate::gpio::{GpioPin, Pull, SealedPin}; /// Sealed marker trait for clockout pins pub trait ClockOutPin: GpioPin + PeripheralType { /// Set the given pin to the correct muxing state fn mux(&self); } macro_rules! impl_pin { ($pin:ident, $func:ident) => { impl ClockOutPin for crate::peripherals::$pin { fn mux(&self) { self.set_function(crate::pac::port::vals::Mux::$func); self.set_pull(Pull::Disabled); // TODO: we may want to expose these as options to allow the slew rate // and drive strength for clocks if they are particularly high speed. // // self.set_drive_strength(crate::pac::port::pcr::Dse::Dse1); // self.set_slew_rate(crate::pac::port::pcr::Sre::Sre0); } } }; } // TODO: 5xx reference manual states that P0_6 and P3_8 are clkout pins (Table 352), but the pinmux // table doesn't list which alt mode corresponds with clkout (Table 340) #[cfg(feature = "mcxa2xx")] #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_6, MUX12); #[cfg(feature = "mcxa2xx")] impl_pin!(P3_8, MUX12); impl_pin!(P3_6, MUX1); impl_pin!(P4_2, MUX1); } ================================================ FILE: embassy-mcxa/src/clocks/config.rs ================================================ //! Clock Configuration //! //! This module holds configuration types used for the system clocks. For //! configuration of individual peripherals, see [`super::periph_helpers`]. use nxp_pac::scg::vals::FreqSel; use super::PoweredClock; /// This type represents a divider in the range 1..=256. /// /// At a hardware level, this is an 8-bit register from 0..=255, /// which adds one. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Div8(pub(super) u8); impl Div8 { /// Store a "raw" divisor value that will divide the source by /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source /// by 1, and `Div8::from_raw(255)` will divide the source by /// 256. pub const fn from_raw(n: u8) -> Self { Self(n) } /// Divide by one, or no division pub const fn no_div() -> Self { Self(0) } /// Store a specific divisor value that will divide the source /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source /// by 1, and `Div8::from_divisor(256)` will divide the source /// by 256. /// /// Will return `None` if `n` is not in the range `1..=256`. /// Consider [`Self::from_raw`] for an infallible version. pub const fn from_divisor(n: u16) -> Option { let Some(n) = n.checked_sub(1) else { return None; }; if n > (u8::MAX as u16) { return None; } Some(Self(n as u8)) } /// Convert into "raw" bits form #[inline(always)] pub const fn into_bits(self) -> u8 { self.0 } /// Convert into "divisor" form, as a u32 for convenient frequency math #[inline(always)] pub const fn into_divisor(self) -> u32 { self.0 as u32 + 1 } } /// ## MCXA2xx /// /// ```text /// ┌─────────────────────────────────────────────────────────┐ /// │ │ /// │ ┌───────────┐ clk_out ┌─────────┐ │ /// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ /// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ /// EXTAL ──────┼──▷│ │───────────▷│ │ │ /// │ └───────────┘ └─────────┘ │ /// │ │ /// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ /// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ /// │ │ or │ │ ├────┤ clk_45m or clk_48m │ /// │ │ FRO192 │ └─────▷│ CG │─────────────────────┼──────▷ /// │ └───────────┘ └────┘ │ /// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ /// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ /// │ │ │ │ ├────┤ clk_1m │ /// │ │ │ └─────▷│1/12│────────────────────┼──────▷ /// │ └───────────┘ └────┘ │ /// │ │ /// │ ┌──────────┐ │ /// │ │000 │ │ /// │ clk_in │ │ │ /// │ ───────────────▷│001 │ │ /// │ fro_12m │ │ │ /// │ ───────────────▷│010 │ │ /// │ fro_hf_root │ │ │ /// │ ───────────────▷│011 │ main_clk │ /// │ │ │───────────────────────────┼──────▷ /// clk_16k ──────┼─────────────────▷│100 │ │ /// │ none │ │ │ /// │ ───────────────▷│101 │ │ /// │ pll1_clk │ │ │ /// │ ───────────────▷│110 │ │ /// │ none │ │ │ /// │ ───────────────▷│111 │ │ /// │ └──────────┘ │ /// │ ▲ │ /// │ │ │ /// │ SCG SCS │ /// │ SCG-Lite │ /// └─────────────────────────────────────────────────────────┘ /// /// /// clk_in ┌─────┐ /// ───────────────▷│00 │ /// clk_45m │ │ /// ───────────────▷│01 │ ┌───────────┐ pll1_clk /// none │ │─────▷│ SPLL │───────────────▷ /// ───────────────▷│10 │ └───────────┘ /// fro_12m │ │ /// ───────────────▷│11 │ /// └─────┘ /// ``` #[non_exhaustive] pub struct ClocksConfig { /// Power states of VDD Core pub vdd_power: VddPowerConfig, /// Clocks that are used to drive the main clock, including the AHB and CPU core pub main_clock: MainClockConfig, /// FIRC /// /// * On MCXA2xx: FRO180, 45/60/90/180M clock source /// * On MCXA5xx: FRO192, 48/64/96/196M clock source pub firc: Option, /// SIRC, FRO12M, clk_12m clock source pub sirc: SircConfig, /// FRO16K clock source pub fro16k: Option, /// OSC32K clock source #[cfg(all(feature = "mcxa5xx", feature = "unstable-osc32k", not(feature = "rosc-32k-as-gpio")))] pub osc32k: Option, /// SOSC, clk_in clock source /// /// NOTE: Requires `sosc-as-gpio` feature disabled, which also disables GPIO access to P1_30 and P1_31 #[cfg(not(feature = "sosc-as-gpio"))] pub sosc: Option, /// SPLL pub spll: Option, } // Power (which is not a clock) /// Selected VDD Power Mode #[derive(Copy, Clone, PartialEq, Debug, Default)] #[non_exhaustive] pub enum VddLevel { /// Standard "mid drive" "MD" power, 1.0v VDD Core #[default] MidDriveMode, /// "Normal" voltage, 1.1v VDD Core #[cfg(feature = "mcxa5xx")] NormalMode, /// Overdrive "OD" power, 1.2v VDD Core OverDriveMode, } #[derive(Copy, Clone, PartialEq)] #[non_exhaustive] pub enum VddDriveStrength { /// Low drive Low { enable_bandgap: bool }, /// Normal drive Normal, } #[derive(Copy, Clone)] #[non_exhaustive] pub struct VddModeConfig { /// VDD_CORE/LDO_CORE voltage level pub level: VddLevel, /// VDD_CORE/LDO_CORE drive strength pub drive: VddDriveStrength, } /// Settings for gating power to on-chip flash /// /// Applies to both "light" WFE sleep, as well as Deep Sleep. Requires that /// /// ## FlashDoze /// /// Disables flash memory accesses and places flash memory in Low-Power state whenever the core clock /// is gated (CKMODE > 0) because of execution of WFI, WFE, or SLEEPONEXIT. Other bus masters that /// attempt to access the flash memory stalls until the core is no longer sleeping. /// /// # FlashWake /// /// Specifies that when this field becomes 1, an attempt to read the flash memory when it is in Low-Power /// state because of FLASHCR\[FLASHDIS\] or FLASHCR\[FLASHDOZE\], causes the flash memory to exit /// Low-Power state for the duration of the flash memory access. #[derive(Copy, Clone, Default)] #[non_exhaustive] pub enum FlashSleep { /// Don't ever set the flash to sleep #[default] Never, /// Set FlashDoze /// /// This setting is only effective if [CoreSleep] has been configured with at least /// the `WfeGated` option or deeper. FlashDoze, /// Set FlashDoze + FlashWake /// /// This setting is only effective if [CoreSleep] has been configured with at least /// the `WfeGated` option or deeper. // // TODO: This *might* be required for DMA out of flash to actually work when // the core is sleeping, otherwise DMA will stall? Needs to be confirmed. FlashDozeWithFlashWake, } /// Maximum sleep depth for the CPU core #[derive(Copy, Clone, Default, Debug)] #[non_exhaustive] pub enum CoreSleep { /// System will sleep using WFE when idle, but the CPU clock domain will not ever /// be gated. This mode uses the most power, but allows for debugging to /// continue uninterrupted. /// /// With this setting, the system never leaves the "Active" configuration mode. #[default] WfeUngated, /// The system will sleep using WFE when idle, and the CPU clock domain will be /// be gated. If configured with [FlashSleep], the internal flash may be gated /// as well. /// /// ## WARNING /// /// Enabling this mode has potential danger to soft-lock the system! /// /// * This mode WILL detach the debugging/RTT/defmt session if active upon first sleep. /// * This mode WILL also require ISP mode recovery in order to re-flash if the core becomes /// "stuck" in sleep. WfeGated, /// The system will go to deep sleep when idle, and the CPU clock domain will be /// be gated. If configured with [FlashSleep], the internal flash may be gated /// as well. /// /// This will also move the system into the "low power" state, which will disable any /// clocks not configured as `PoweredClock::AlwaysActive". /// /// ## TODO /// /// For now, this REQUIRES calling unsafe `okay_but_actually_enable_deep_sleep()` /// otherwise we'd ALWAYS go to deep sleep on every WFE. We need to implement a /// custom executor that does proper go-to-deepsleep and come-back-from-deepsleep /// before un-chickening this. If the method isn't called, we just set to `WfeGated` /// instead. /// /// ## WARNING /// /// Enabling this mode has potential danger to soft-lock the system! /// /// * This mode WILL detach the debugging/RTT/defmt session if active upon first sleep. /// * This mode WILL also require ISP mode recovery in order to re-flash if the core becomes /// "stuck" in sleep. DeepSleep, } /// Power control options for the VDD domain, including the CPU and flash memory #[derive(Copy, Clone)] #[non_exhaustive] pub struct VddPowerConfig { /// Active power mode, used when not in Deep Sleep pub active_mode: VddModeConfig, /// Low power mode, used when in Deep Sleep pub low_power_mode: VddModeConfig, /// CPU core clock gating settings pub core_sleep: CoreSleep, /// Internal flash clock gating settings pub flash_sleep: FlashSleep, } // Main Clock /// Main clock source #[derive(Copy, Clone)] pub enum MainClockSource { /// Clock derived from `clk_in`, via the external oscillator (8-50MHz) /// /// NOTE: Requires `sosc-as-gpio` feature disabled, which also disables GPIO access to P1_30 and P1_31 #[cfg(not(feature = "sosc-as-gpio"))] SoscClkIn, /// Clock derived from `fro_12m`, via the internal 12MHz oscillator (12MHz) SircFro12M, /// Clock derived from `fro_hf_root`, via the internal 45/60/90/180M clock source (45-180MHz) FircHfRoot, /// Clock derived from `clk_16k` (vdd core) #[cfg(feature = "mcxa2xx")] RoscFro16K, /// Clock derived from `clk_32k` (vdd core) #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] RoscOsc32K, /// Clock derived from `pll1_clk`, via the internal PLL SPll1, } #[derive(Copy, Clone)] pub struct MainClockConfig { /// Selected clock source pub source: MainClockSource, /// Power state of the main clock pub power: PoweredClock, /// AHB Clock Divider pub ahb_clk_div: Div8, } // SOSC /// The mode of the external reference clock #[derive(Copy, Clone)] pub enum SoscMode { /// Passive crystal oscillators CrystalOscillator, /// Active external reference clock ActiveClock, } /// SOSC/clk_in configuration #[derive(Copy, Clone)] pub struct SoscConfig { /// Mode of the external reference clock pub mode: SoscMode, /// Specific frequency of the external reference clock pub frequency: u32, /// Power state of the external reference clock pub power: PoweredClock, } // SPLL /// PLL1/SPLL configuration pub struct SpllConfig { /// Input clock source for the PLL1/SPLL pub source: SpllSource, /// Mode of operation for the PLL1/SPLL pub mode: SpllMode, /// Power state of the SPLL pub power: PoweredClock, /// Is the "pll1_clk_div" clock enabled? pub pll1_clk_div: Option, } /// Input clock source for the PLL1/SPLL pub enum SpllSource { /// External Oscillator (8-50MHz) #[cfg(not(feature = "sosc-as-gpio"))] Sosc, /// Fast Internal Oscillator /// /// * MCXA2xx NOTE: Figure 69 says "firc_45mhz"/"clk_45m", not "fro_hf_gated", /// so this is always 45MHz. /// * MCXA5xx NOTE: Figure 116 says "clk_48m", so this is always 48MHz. Firc, /// S Internal Oscillator (12M) Sirc, // TODO: the reference manual hints that ROSC is possible, // however the minimum input frequency is 32K, but ROSC is 16K. // Some diagrams show this option, and some diagrams omit it. // SVD shows it as "reserved". // // TODO(AJM): Re-enable for MCXA5xx // // /// Realtime Internal Oscillator (16K Osc) // Rosc, } /// Mode of operation for the SPLL/PLL1 /// /// NOTE: Currently, only "Mode 1" normal operational modes are implemented, /// as described in the Reference Manual. #[non_exhaustive] pub enum SpllMode { /// Mode 1a does not use the Pre/Post dividers. /// /// `Fout = m_mult x SpllSource` /// /// Both of the following constraints must be met: /// /// * Fout: 275MHz to 550MHz /// * Fout: 4.3MHz to 2x Max CPU Frequency Mode1a { /// PLL Multiplier. Must be in the range 1..=65535. m_mult: u16, }, /// Mode 1b does not use the Pre-divider. /// /// * `if !bypass_p2_div: Fout = (M / (2 x P)) x Fin` /// * `if bypass_p2_div: Fout = (M / P ) x Fin` /// /// Both of the following constraints must be met: /// /// * Fcco: 275MHz to 550MHz /// * `Fcco = m_mult x SpllSource` /// * Fout: 4.3MHz to 2x Max CPU Frequency Mode1b { /// PLL Multiplier. `m_mult` must be in the range 1..=65535. m_mult: u16, /// Post Divider. `p_div` must be in the range 1..=31. p_div: u8, /// Bonus post divider bypass_p2_div: bool, }, /// Mode 1c does use the Pre-divider, but does not use the Post-divider /// /// `Fout = (M / N) x Fin` /// /// Both of the following constraints must be met: /// /// * Fout: 275MHz to 550MHz /// * Fout: 4.3MHz to 2x Max CPU Frequency Mode1c { /// PLL Multiplier. `m_mult` must be in the range 1..=65535. m_mult: u16, /// Pre Divider. `n_div` must be in the range 1..=255. n_div: u8, }, /// Mode 1b uses both the Pre and Post dividers. /// /// * `if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin` /// * `if bypass_p2_div: Fout = (M / ( N x P )) x Fin` /// /// Both of the following constraints must be met: /// /// * Fcco: 275MHz to 550MHz /// * `Fcco = (m_mult x SpllSource) / (n_div x p_div (x 2))` /// * Fout: 4.3MHz to 2x Max CPU Frequency Mode1d { /// PLL Multiplier. `m_mult` must be in the range 1..=65535. m_mult: u16, /// Pre Divider. `n_div` must be in the range 1..=255. n_div: u8, /// Post Divider. `p_div` must be in the range 1..=31. p_div: u8, /// Bonus post divider bypass_p2_div: bool, }, } // FIRC/FRO180M /// ```text /// ┌───────────┐ fro_hf_root ┌────┐ fro_hf /// │ FRO180M ├───────┬─────▷│GATE│──────────▷ /// │ or │ │ ├────┤ clk_45m/clk_48m /// │ FRO192M │ └─────▷│GATE│──────────▷ /// └───────────┘ └────┘ /// ``` #[non_exhaustive] pub struct FircConfig { /// Selected clock frequency pub frequency: FircFreqSel, /// Selected power state of the clock pub power: PoweredClock, /// Is the "fro_hf" gated clock enabled? pub fro_hf_enabled: bool, /// Is the "clk_45m"/"clk_48m" gated clock enabled? pub clk_hf_fundamental_enabled: bool, /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! pub fro_hf_div: Option, } /// Selected FIRC frequency #[cfg(feature = "mcxa2xx")] #[derive(Debug, PartialEq)] pub enum FircFreqSel { /// 45MHz Output Mhz45, /// 60MHz Output Mhz60, /// 90MHz Output Mhz90, /// 180MHz Output Mhz180, } /// Selected FIRC frequency #[cfg(feature = "mcxa5xx")] #[derive(Debug, PartialEq)] pub enum FircFreqSel { /// 48MHz Output Mhz48, /// 64MHz Output Mhz64, /// 96MHz Output Mhz96, /// 192MHz Output Mhz192, } impl FircFreqSel { #[cfg(feature = "mcxa2xx")] pub(crate) fn to_freq_and_sel(&self) -> (u32, FreqSel) { // NOTE: the SVD currently has the wrong(?) values for these: // 45 -> 48 // 60 -> 64 // 90 -> 96 // 180 -> 192 // // Probably correct-ish, but for a different trim value? match self { FircFreqSel::Mhz45 => { // We are default, there's nothing to do here. (45_000_000, FreqSel::FIRC_48MHZ_192S) } FircFreqSel::Mhz60 => (60_000_000, FreqSel::FIRC_64MHZ), FircFreqSel::Mhz90 => (90_000_000, FreqSel::FIRC_96MHZ), FircFreqSel::Mhz180 => (180_000_000, FreqSel::FIRC_192MHZ), } } #[cfg(feature = "mcxa5xx")] pub(crate) fn to_freq_and_sel(&self) -> (u32, FreqSel) { match self { FircFreqSel::Mhz48 => { // We are default, there's nothing to do here. (48_000_000, FreqSel::FIRC_48MHZ_192S) } FircFreqSel::Mhz64 => (64_000_000, FreqSel::FIRC_64MHZ), FircFreqSel::Mhz96 => (96_000_000, FreqSel::FIRC_96MHZ), FircFreqSel::Mhz192 => (192_000_000, FreqSel::FIRC_192MHZ), } } } // SIRC/FRO12M /// ```text /// ┌───────────┐ fro_12m_root ┌────┐ fro_12m /// │ FRO12M │────────┬─────▷│ CG │──────────▷ /// │ │ │ ├────┤ clk_1m /// │ │ └─────▷│1/12│──────────▷ /// └───────────┘ └────┘ /// ``` #[non_exhaustive] pub struct SircConfig { pub power: PoweredClock, // peripheral output, aka sirc_12mhz pub fro_12m_enabled: bool, /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! pub fro_lf_div: Option, } /// FRO16K Configuration items #[non_exhaustive] pub struct Fro16KConfig { /// is `clk_16k[0]` active? pub vsys_domain_active: bool, /// is `clk_16k[1]` active? pub vdd_core_domain_active: bool, /// is `clk_16k[2]` active? #[cfg(feature = "mcxa5xx")] pub vbat_domain_active: bool, } impl Default for Fro16KConfig { fn default() -> Self { Self { vsys_domain_active: true, vdd_core_domain_active: true, #[cfg(feature = "mcxa5xx")] vbat_domain_active: true, } } } /// OSC32K Operational Mode #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] pub enum Osc32KMode { /// low power switched oscillator mode LowPower { /// 32K Oscillator internal transconductance gain current coarse_amp_gain: Osc32KCoarseGain, /// Enable if Vbat exceeds 3.0v vbat_exceeds_3v0: bool, }, /// high performance transconductance oscillator mode HighPower { /// 32K Oscillator internal transconductance gain current coarse_amp_gain: Osc32KCoarseGain, /// Configurable capacitance for XTAL pad xtal_cap_sel: Osc32KCapSel, /// Configurable capacitance for EXTAL pad extal_cap_sel: Osc32KCapSel, }, } /// Coarse Gain Amplification /// /// See datasheet table 4.2.1.4, "32 kHz oscillation gain setting" #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] pub enum Osc32KCoarseGain { /// Max ESR 50kOhms, Max Cx 14pF EsrRange0, /// Max ESR 70kOhms, Max Cx 22pF EsrRange1, /// Max ESR 80kOhms, Max Cx 22pF EsrRange2, /// Max ESR 100kOhms, Max Cx 20pF EsrRange3, } #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] pub enum Osc32KCapSel { // NOTE: 0pF is not supported in non-low-power-modes /// 2pF Cap2PicoF, /// 4pF Cap4PicoF, /// 6pF Cap6PicoF, /// 8pF Cap8PicoF, /// 10pF Cap10PicoF, /// 12pF Cap12PicoF, /// 14pF Cap14PicoF, /// 16pF Cap16PicoF, /// 18pF Cap18PicoF, /// 20pF Cap20PicoF, /// 22pF Cap22PicoF, /// 24pF Cap24PicoF, /// 26pF Cap26PicoF, /// 28pF Cap28PicoF, /// 30pF Cap30PicoF, } /// OSC32K Configuration Items #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] #[non_exhaustive] pub struct Osc32KConfig { /// Low/High Power Mode Selection pub mode: Osc32KMode, /// is `clk_32k[0]` active? pub vsys_domain_active: bool, /// is `clk_32k[1]` active? pub vdd_core_domain_active: bool, /// is `clk_32k[2]` active? pub vbat_domain_active: bool, } #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] impl Default for Osc32KConfig { fn default() -> Self { Self { mode: Osc32KMode::LowPower { coarse_amp_gain: Osc32KCoarseGain::EsrRange0, vbat_exceeds_3v0: true, }, vsys_domain_active: true, vdd_core_domain_active: true, vbat_domain_active: true, } } } impl Default for FircConfig { fn default() -> Self { FircConfig { #[cfg(feature = "mcxa2xx")] frequency: FircFreqSel::Mhz45, #[cfg(feature = "mcxa5xx")] frequency: FircFreqSel::Mhz48, power: PoweredClock::NormalEnabledDeepSleepDisabled, fro_hf_enabled: true, clk_hf_fundamental_enabled: true, fro_hf_div: None, } } } impl Default for ClocksConfig { fn default() -> Self { Self { vdd_power: VddPowerConfig { active_mode: VddModeConfig { level: VddLevel::MidDriveMode, drive: VddDriveStrength::Normal, }, low_power_mode: VddModeConfig { level: VddLevel::MidDriveMode, drive: VddDriveStrength::Normal, }, core_sleep: CoreSleep::WfeUngated, flash_sleep: FlashSleep::Never, }, main_clock: MainClockConfig { source: MainClockSource::FircHfRoot, power: PoweredClock::NormalEnabledDeepSleepDisabled, ahb_clk_div: Div8::no_div(), }, firc: Some(FircConfig { #[cfg(feature = "mcxa2xx")] frequency: FircFreqSel::Mhz45, #[cfg(feature = "mcxa5xx")] frequency: FircFreqSel::Mhz48, power: PoweredClock::NormalEnabledDeepSleepDisabled, fro_hf_enabled: true, clk_hf_fundamental_enabled: true, fro_hf_div: None, }), sirc: SircConfig { power: PoweredClock::AlwaysEnabled, fro_12m_enabled: true, fro_lf_div: None, }, fro16k: Some(Fro16KConfig { vsys_domain_active: true, vdd_core_domain_active: true, #[cfg(feature = "mcxa5xx")] vbat_domain_active: true, }), #[cfg(all(feature = "mcxa5xx", feature = "unstable-osc32k", not(feature = "rosc-32k-as-gpio")))] osc32k: None, #[cfg(not(feature = "sosc-as-gpio"))] sosc: None, spll: None, } } } ================================================ FILE: embassy-mcxa/src/clocks/gate.rs ================================================ //! Clock gate trait and free functions for enabling/disabling peripheral clocks. use super::CLOCKS; use super::periph_helpers::{PreEnableParts, SPConfHelper}; use super::types::ClockError; /// Trait describing an AHB clock gate that can be toggled through MRCC. pub trait Gate { type MrccPeriphConfig: SPConfHelper; /// Enable the clock gate. /// /// # SAFETY /// /// The current peripheral must be disabled prior to calling this method unsafe fn enable_clock(); /// Disable the clock gate. /// /// # SAFETY /// /// There must be no active user of this peripheral when calling this method unsafe fn disable_clock(); /// Drive the peripheral into reset. /// /// # SAFETY /// /// There must be no active user of this peripheral when calling this method unsafe fn assert_reset(); /// Drive the peripheral out of reset. /// /// # SAFETY /// /// There must be no active user of this peripheral when calling this method unsafe fn release_reset(); /// Return whether the clock gate for this peripheral is currently enabled. fn is_clock_enabled() -> bool; /// Return whether the peripheral is currently held in reset. fn is_reset_released() -> bool; } /// This is the primary helper method HAL drivers are expected to call when creating /// an instance of the peripheral. /// /// This method: /// /// 1. Enables the MRCC clock gate for this peripheral /// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error /// and re-disabling the peripheral if this fails. /// 3. Pulses the MRCC reset line, to reset the peripheral to the default state /// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account /// the selected upstream clock, as well as any division specified by `cfg`. /// /// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method /// may return `Ok(0)`. In the future, this might be updated to return the correct /// "ambient" clock, e.g. the AHB/APB frequency. /// /// # SAFETY /// /// This peripheral must not yet be in use prior to calling `enable_and_reset`. #[inline] pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { unsafe { let freq = enable::(cfg)?; pulse_reset::(); Ok(freq) } } /// Enable the clock gate for the given peripheral. /// /// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need /// to control the duration of the pulse more directly. /// /// If an `Err` is returned, the given clock is guaranteed to be disabled. /// /// # SAFETY /// /// This peripheral must not yet be in use prior to calling `enable`. #[inline] pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { unsafe { // Instead of checking, just disable the clock if it is currently enabled. G::disable_clock(); let freq = critical_section::with(|cs| { let clocks = CLOCKS.borrow_ref(cs); let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; cfg.pre_enable_config(clocks) })?; G::enable_clock(); while !G::is_clock_enabled() {} core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); Ok(freq) } } /// Disable the clock gate for the given peripheral. /// /// # SAFETY /// /// This peripheral must no longer be in use prior to calling `enable`. #[allow(dead_code)] #[inline] pub unsafe fn disable() { unsafe { G::disable_clock(); } } /// Check whether a gate is currently enabled. #[allow(dead_code)] #[inline] pub fn is_clock_enabled() -> bool { G::is_clock_enabled() } /// Release a reset line for the given peripheral set. /// /// Prefer [`enable_and_reset`]. /// /// # SAFETY /// /// This peripheral must not yet be in use prior to calling `release_reset`. #[inline] pub unsafe fn release_reset() { unsafe { G::release_reset(); } } /// Assert a reset line for the given peripheral set. /// /// Prefer [`enable_and_reset`]. /// /// # SAFETY /// /// This peripheral must not yet be in use prior to calling `assert_reset`. #[inline] pub unsafe fn assert_reset() { unsafe { G::assert_reset(); } } /// Check whether the peripheral is held in reset. /// /// # Safety /// /// Must be called with a valid peripheral gate type. #[inline] pub unsafe fn is_reset_released() -> bool { G::is_reset_released() } /// Pulse a reset line (assert then release) with a short delay. /// /// Prefer [`enable_and_reset`]. /// /// # SAFETY /// /// This peripheral must not yet be in use prior to calling `release_reset`. #[inline] pub unsafe fn pulse_reset() { unsafe { G::assert_reset(); cortex_m::asm::nop(); cortex_m::asm::nop(); G::release_reset(); } } // // Macros/macro impls // /// This macro is used to implement the [`Gate`] trait for a given peripheral /// that is controlled by the MRCC peripheral. #[doc(hidden)] #[macro_export] macro_rules! impl_cc_gate { ($name:ident, $clk_reg:ident, $field:ident, $config:ty) => { impl Gate for $crate::peripherals::$name { type MrccPeriphConfig = $config; paste! { #[inline] unsafe fn enable_clock() { pac::MRCC0.$clk_reg().modify(|w| w.[](true)); } #[inline] unsafe fn disable_clock() { pac::MRCC0.$clk_reg().modify(|w| w.[](false)); } #[inline] unsafe fn release_reset() {} #[inline] unsafe fn assert_reset() {} } #[inline] fn is_clock_enabled() -> bool { pac::MRCC0.$clk_reg().read().$field() } #[inline] fn is_reset_released() -> bool { false } } }; ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { impl Gate for $crate::peripherals::$name { type MrccPeriphConfig = $config; paste! { #[inline] unsafe fn enable_clock() { pac::MRCC0.$clk_reg().modify(|w| w.[](true)); } #[inline] unsafe fn disable_clock() { pac::MRCC0.$clk_reg().modify(|w| w.[](false)); } #[inline] unsafe fn release_reset() { pac::MRCC0.$rst_reg().modify(|w| w.[](true)); // Wait for reset to set while !pac::MRCC0.$rst_reg().read().[<$field>]() {} } #[inline] unsafe fn assert_reset() { pac::MRCC0.$rst_reg().modify(|w| w.[](false)); // Wait for reset to clear while pac::MRCC0.$rst_reg().read().[<$field>]() {} } } #[inline] fn is_clock_enabled() -> bool { pac::MRCC0.$clk_reg().read().$field() } #[inline] fn is_reset_released() -> bool { pac::MRCC0.$rst_reg().read().$field() } } }; } ================================================ FILE: embassy-mcxa/src/clocks/mod.rs ================================================ //! # Clock Module //! //! For the MCX-A, we separate clock and peripheral control into two main stages: //! //! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the //! core system clocks, including external and internal oscillators. This //! configuration is then largely static for the duration of the program. //! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver //! is responsible for two main things: //! * Ensuring that any required "upstream" core system clocks necessary for //! clocking the peripheral is active and configured to a reasonable value //! * Enabling the clock gates for that peripheral, and resetting the peripheral //! //! From a user perspective, only step 1 is visible. Step 2 is automatically handled //! by HAL drivers, using interfaces defined in this module. //! //! It is also possible to *view* the state of the clock configuration after [`init()`] //! has been called, using the [`with_clocks()`] function, which provides a view of the //! [`Clocks`] structure. //! //! ## For HAL driver implementors //! //! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or //! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This //! selection, as well as often any pre-scaler division from that source clock, is made //! through MRCC registers. //! //! Any peripheral that is controlled through the MRCC register can automatically implement //! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need //! to define the configuration surface and steps necessary to fully configure that peripheral //! from a clocks perspective by: //! //! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects //! or divisions available to the HAL driver //! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the //! necessary input clocks are reasonable use core::cell::RefCell; use core::sync::atomic::{AtomicUsize, Ordering}; use config::ClocksConfig; use critical_section::CriticalSection; use crate::pac; pub mod config; mod gate; mod operator; pub mod periph_helpers; mod sleep; mod types; // Re-exports pub use config::VddLevel; pub use gate::{Gate, assert_reset, disable, enable, enable_and_reset, is_reset_released, release_reset}; pub use sleep::deep_sleep_if_possible; pub use types::{Clock, ClockError, Clocks, PoweredClock, WakeGuard}; // // Statics/Consts // /// The state of system core clocks. /// /// Initialized by [`init()`], and then unchanged for the remainder of the program. pub(super) static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); pub(super) static LIVE_HP_TOKENS: AtomicUsize = AtomicUsize::new(0); // // Free functions // /// Initialize the core system clocks with the given [`ClocksConfig`]. /// /// This function should be called EXACTLY once at start-up, usually via a /// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will /// return an error. pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { critical_section::with(|cs| { if CLOCKS.borrow_ref(cs).is_some() { Err(ClockError::AlreadyInitialized) } else { Ok(()) } })?; let mut clocks = Clocks::default(); let mut operator = operator::ClockOperator { clocks: &mut clocks, config: &settings, sirc_forced: false, _mrcc0: pac::MRCC0, scg0: pac::SCG0, syscon: pac::SYSCON, vbat0: pac::VBAT0, spc0: pac::SPC0, fmu0: pac::FMU0, cmc: pac::CMC, }; operator.unlock_mrcc(); // Before applying any requested clocks, apply the requested VDD_CORE // voltage level operator.configure_voltages()?; // Enable SIRC clocks FIRST, in case we need to use SIRC as main_clk for // a short while. operator.configure_sirc_clocks_early()?; operator.configure_firc_clocks()?; operator.configure_fro16k_clocks()?; // NOTE: OSC32K must be configured AFTER FRO16K. #[cfg(all(feature = "mcxa5xx", feature = "unstable-osc32k", not(feature = "rosc-32k-as-gpio")))] operator.configure_osc32k_clocks()?; #[cfg(not(feature = "sosc-as-gpio"))] operator.configure_sosc()?; operator.configure_spll()?; // Finally, setup main clock operator.configure_main_clk()?; // If we were keeping SIRC enabled, now we can release it. operator.configure_sirc_clocks_late(); critical_section::with(|cs| { let mut clks = CLOCKS.borrow_ref_mut(cs); assert!(clks.is_none(), "Clock setup race!"); *clks = Some(clocks); }); Ok(()) } /// Obtain the full clocks structure, calling the given closure in a critical section. /// /// The given closure will be called with read-only access to the state of the system /// clocks. This can be used to query and return the state of a given clock. /// /// As the caller's closure will be called in a critical section, care must be taken /// not to block or cause any other undue delays while accessing. /// /// Calls to this function will not succeed until after a successful call to `init()`, /// and will always return None. pub fn with_clocks R>(f: F) -> Option { critical_section::with(|cs| { let c = CLOCKS.borrow_ref(cs); let c = c.as_ref()?; Some(f(c)) }) } /// Are there active `WakeGuard`s? /// /// Requires a critical section to ensure this doesn't race between getting the guard /// count and performing some action like setting up deep sleep #[inline(always)] pub fn active_wake_guards(_cs: &CriticalSection) -> bool { // Relaxed is okay: we are in a critical section LIVE_HP_TOKENS.load(Ordering::Relaxed) != 0 } ================================================ FILE: embassy-mcxa/src/clocks/operator.rs ================================================ //! `ClockOperator` — init-time clock configuration sequencing. //! //! This module contains the private `ClockOperator` struct and all of its //! `configure_*` methods. It is only used during [`super::init()`]. use config::{ ClocksConfig, CoreSleep, FircConfig, FircFreqSel, Fro16KConfig, MainClockSource, SircConfig, VddDriveStrength, VddLevel, }; use cortex_m::peripheral::SCB; use nxp_pac::syscon::vals::Unlock; use super::config; use super::types::{Clock, ClockError, Clocks, PoweredClock}; use crate::chips::{ClockLimits, clock_limits}; use crate::pac; use crate::pac::cmc::vals::CkctrlCkmode; use crate::pac::scg::vals::{ Erefs, Fircacc, FircaccIe, FirccsrLk, Fircerr, FircerrIe, Fircsten, Range, Scs, SirccsrLk, Sircerr, Sircvld, SosccsrLk, Soscerr, Source, SpllLock, SpllcsrLk, Spllerr, Spllsten, TrimUnlock, }; use crate::pac::spc::vals::{ ActiveCfgBgmode, ActiveCfgCoreldoVddDs, ActiveCfgCoreldoVddLvl, LpCfgBgmode, LpCfgCoreldoVddLvl, Vsm, }; use crate::pac::syscon::vals::{ AhbclkdivUnstab, FrohfdivHalt, FrohfdivReset, FrohfdivUnstab, FrolfdivHalt, FrolfdivReset, FrolfdivUnstab, Pll1clkdivHalt, Pll1clkdivReset, Pll1clkdivUnstab, }; /// The ClockOperator is a private helper type that contains the methods used /// during system clock initialization. /// /// # SAFETY /// /// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, /// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`](super::init) function. #[allow(dead_code)] pub(super) struct ClockOperator<'a> { /// A mutable reference to the current state of system clocks pub(super) clocks: &'a mut Clocks, /// A reference to the requested configuration provided by the caller of [`init()`](super::init) pub(super) config: &'a ClocksConfig, /// SIRC is forced-on until we set `main_clk` pub(super) sirc_forced: bool, // We hold on to stolen peripherals pub(super) _mrcc0: pac::mrcc::Mrcc, pub(super) scg0: pac::scg::Scg, pub(super) syscon: pac::syscon::Syscon, pub(super) vbat0: pac::vbat::Vbat, pub(super) spc0: pac::spc::Spc, pub(super) fmu0: pac::fmu::Fmu, pub(super) cmc: pac::cmc::Cmc, } impl ClockOperator<'_> { pub(super) fn unlock_mrcc(&mut self) { // On the MCXA5xx, this is default *locked*, preventing any writes to // MRCC registers re enable/div settings. For now, just leave it unlocked, // we might want to actively unlock/lock in periph helpers in the future. self.syscon.clkunlock().modify(|w| w.set_unlock(Unlock::ENABLE)); } fn active_limits(&self) -> &'static ClockLimits { match self.config.vdd_power.active_mode.level { VddLevel::MidDriveMode => &ClockLimits::MID_DRIVE, #[cfg(feature = "mcxa5xx")] VddLevel::NormalMode => &ClockLimits::NORMAL_DRIVE, VddLevel::OverDriveMode => &ClockLimits::OVER_DRIVE, } } fn low_power_limits(&self) -> &'static ClockLimits { match self.config.vdd_power.low_power_mode.level { VddLevel::MidDriveMode => &ClockLimits::MID_DRIVE, #[cfg(feature = "mcxa5xx")] VddLevel::NormalMode => &ClockLimits::NORMAL_DRIVE, VddLevel::OverDriveMode => &ClockLimits::OVER_DRIVE, } } fn lowest_relevant_limits(&self, for_power: &PoweredClock) -> &'static ClockLimits { // We always enforce that deep sleep has a drive <= active mode. match for_power { PoweredClock::NormalEnabledDeepSleepDisabled => self.active_limits(), PoweredClock::AlwaysEnabled => self.low_power_limits(), } } /// Configure the FIRC/FRO180M/FRO192M clock family pub(super) fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { // Three options here: // // * Firc is disabled -> Switch main clock to SIRC and return // * Firc is enabled and !default -> // * Switch main clock to SIRC // * Make FIRC changes // * Switch main clock back to FIRC // * Firc is enabled and default -> nop #[cfg(feature = "mcxa2xx")] let default_freq = FircFreqSel::Mhz45; #[cfg(feature = "mcxa5xx")] let default_freq = FircFreqSel::Mhz48; let is_default = self.config.firc.as_ref().is_some_and(|c| c.frequency == default_freq); // If we are not default, then we need to switch to SIRC if !is_default { // Set SIRC (fro_12m) as the source self.scg0.rccr().modify(|w| w.set_scs(Scs::SIRC)); // Wait for the change to complete while self.scg0.csr().read().scs() != Scs::SIRC {} } // Enable CSR writes self.scg0.firccsr().modify(|w| w.set_lk(FirccsrLk::WRITE_ENABLED)); // Did the user give us a FIRC config? let Some(firc) = self.config.firc.as_ref() else { // Nope, and we've already switched to fro_12m. Disable FIRC. self.scg0.firccsr().modify(|w| { w.set_fircsten(Fircsten::DISABLED_IN_STOP_MODES); w.set_fircerr_ie(FircerrIe::ERROR_NOT_DETECTED); w.set_firc_fclk_periph_en(false); w.set_firc_sclk_periph_en(false); w.set_fircen(false); }); self.scg0.firccsr().modify(|w| w.set_lk(FirccsrLk::WRITE_DISABLED)); return Ok(()); }; // If we are here, we WANT FIRC. If we are !default, let's disable FIRC before // we mess with it. If we are !default, we have already switched to SIRC instead! if !is_default { // Unlock self.scg0.firccsr().modify(|w| w.set_lk(FirccsrLk::WRITE_ENABLED)); // Disable FIRC self.scg0.firccsr().modify(|w| { w.set_fircen(false); w.set_fircsten(Fircsten::DISABLED_IN_STOP_MODES); w.set_fircerr_ie(FircerrIe::ERROR_NOT_DETECTED); w.set_fircacc_ie(FircaccIe::FIRCACCNOT); w.set_firc_sclk_periph_en(false); w.set_firc_fclk_periph_en(false); }); } let limits = self.lowest_relevant_limits(&firc.power); // Set frequency (if not the default!), re-enable FIRC, and return the base frequency let (base_freq, sel) = firc.frequency.to_freq_and_sel(); self.scg0.firccfg().modify(|w| w.set_freq_sel(sel)); self.scg0.firccsr().modify(|w| w.set_fircen(true)); // Wait for FIRC to be enabled, error-free, and accurate let mut firc_ok = false; while !firc_ok { let csr = self.scg0.firccsr().read(); firc_ok = csr.fircen() && csr.fircacc() == Fircacc::ENABLED_AND_VALID && csr.fircerr() == Fircerr::ERROR_NOT_DETECTED; } // Note that the fro_hf_root is active self.clocks.fro_hf_root = Some(Clock { frequency: base_freq, power: firc.power, }); // Okay! Now we're past that, let's enable all the downstream clocks. let FircConfig { frequency: _, power, fro_hf_enabled, clk_hf_fundamental_enabled, fro_hf_div, } = firc; // When is the FRO enabled? let (bg_good, pow_set) = match power { PoweredClock::NormalEnabledDeepSleepDisabled => { // We only need bandgap enabled in active mode (self.clocks.bandgap_active, Fircsten::DISABLED_IN_STOP_MODES) } PoweredClock::AlwaysEnabled => { // We need bandgaps enabled in both active and deep sleep mode let bg_good = self.clocks.bandgap_active && self.clocks.bandgap_lowpower; (bg_good, Fircsten::ENABLED_IN_STOP_MODES) } }; if !bg_good { return Err(ClockError::BadConfig { clock: "fro_hf", reason: "bandgap required to be enabled when clock enabled", }); } // Do we enable the `fro_hf` output? let fro_hf_set = if *fro_hf_enabled { if base_freq > limits.fro_hf { return Err(ClockError::BadConfig { clock: "fro_hf", reason: "exceeds max", }); } self.clocks.fro_hf = Some(Clock { frequency: base_freq, power: *power, }); true } else { false }; // Do we enable the `clk_45m`/`clk_48m` output? let clk_fund_set = if *clk_hf_fundamental_enabled { self.clocks.clk_hf_fundamental = Some(Clock { frequency: 45_000_000, power: *power, }); true } else { false }; self.scg0.firccsr().modify(|w| { w.set_fircsten(pow_set); w.set_firc_fclk_periph_en(fro_hf_set); w.set_firc_sclk_periph_en(clk_fund_set); }); // Last write to CSR, re-lock self.scg0.firccsr().modify(|w| w.set_lk(FirccsrLk::WRITE_DISABLED)); // Do we enable the `fro_hf_div` output? if let Some(d) = fro_hf_div.as_ref() { // We need `fro_hf` to be enabled if !*fro_hf_enabled { return Err(ClockError::BadConfig { clock: "fro_hf_div", reason: "fro_hf not enabled", }); } let div_freq = base_freq / d.into_divisor(); if div_freq > limits.fro_hf_div { return Err(ClockError::BadConfig { clock: "fro_hf_root", reason: "exceeds max frequency", }); } // Halt and reset the div; then set our desired div. self.syscon.frohfdiv().write(|w| { w.set_halt(FrohfdivHalt::HALT); w.set_reset(FrohfdivReset::ASSERTED); w.set_div(d.into_bits()); }); // Then unhalt it, and reset it self.syscon.frohfdiv().write(|w| { w.set_halt(FrohfdivHalt::RUN); w.set_reset(FrohfdivReset::RELEASED); w.set_div(d.into_bits()); }); // Wait for clock to stabilize while self.syscon.frohfdiv().read().unstab() == FrohfdivUnstab::ONGOING {} // Store off the clock info self.clocks.fro_hf_div = Some(Clock { frequency: div_freq, power: *power, }); } Ok(()) } /// Configure the SIRC/FRO12M clock family pub(super) fn configure_sirc_clocks_early(&mut self) -> Result<(), ClockError> { let SircConfig { power, fro_12m_enabled, fro_lf_div, } = &self.config.sirc; let base_freq = 12_000_000; // Allow writes self.scg0.sirccsr().modify(|w| w.set_lk(SirccsrLk::WRITE_ENABLED)); self.clocks.fro_12m_root = Some(Clock { frequency: base_freq, power: *power, }); let deep = match power { PoweredClock::NormalEnabledDeepSleepDisabled => false, PoweredClock::AlwaysEnabled => true, }; // clk_1m is *before* the fro_12m clock gate self.clocks.clk_1m = Some(Clock { frequency: base_freq / 12, power: *power, }); // If the user wants fro_12m to be disabled, FOR now, we ignore their // wish to ensure fro_12m is selectable as a main_clk source at least until // we select the CPU clock. We still mark it as not enabled though, to prevent // other peripherals using it, as we will gate if off at `configure_sirc_clocks_late`. if *fro_12m_enabled { self.clocks.fro_12m = Some(Clock { frequency: base_freq, power: *power, }); } else { self.sirc_forced = true; }; // Set sleep/peripheral usage self.scg0.sirccsr().modify(|w| { w.set_sircsten(deep); // Always on, for now at least! Will be resolved in `configure_sirc_clocks_late` w.set_sirc_clk_periph_en(true); }); while self.scg0.sirccsr().read().sircvld() == Sircvld::DISABLED_OR_NOT_VALID {} if self.scg0.sirccsr().read().sircerr() == Sircerr::ERROR_DETECTED { return Err(ClockError::BadConfig { clock: "sirc", reason: "error set", }); } // reset lock self.scg0.sirccsr().modify(|w| w.set_lk(SirccsrLk::WRITE_DISABLED)); // Do we enable the `fro_lf_div` output? if let Some(d) = fro_lf_div.as_ref() { // We need `fro_lf` to be enabled if !*fro_12m_enabled { return Err(ClockError::BadConfig { clock: "fro_lf_div", reason: "fro_12m not enabled", }); } // Halt and reset the div; then set our desired div. self.syscon.frolfdiv().write(|w| { w.set_halt(FrolfdivHalt::HALT); w.set_reset(FrolfdivReset::ASSERTED); w.set_div(d.into_bits()); }); // Then unhalt it, and reset it self.syscon.frolfdiv().modify(|w| { w.set_halt(FrolfdivHalt::RUN); w.set_reset(FrolfdivReset::RELEASED); w.set_div(d.into_bits()); }); // Wait for clock to stabilize while self.syscon.frolfdiv().read().unstab() == FrolfdivUnstab::ONGOING {} // Store off the clock info self.clocks.fro_lf_div = Some(Clock { frequency: base_freq / d.into_divisor(), power: *power, }); } Ok(()) } pub(super) fn configure_sirc_clocks_late(&mut self) { // If we forced SIRC's fro_12m to be enabled, disable it now. if self.sirc_forced { // Allow writes self.scg0.sirccsr().modify(|w| w.set_lk(SirccsrLk::WRITE_ENABLED)); // Disable clk_12m self.scg0.sirccsr().modify(|w| w.set_sirc_clk_periph_en(false)); // reset lock self.scg0.sirccsr().modify(|w| w.set_lk(SirccsrLk::WRITE_DISABLED)); } } /// Configure the ROSC/FRO16K/clk_16k clock family pub(super) fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { // If we have a config: ensure fro16k is enabled. If not: ensure it is disabled. let enable = self.config.fro16k.is_some(); self.vbat0.froctla().modify(|w| w.set_fro_en(enable)); // Lock the control register self.vbat0.frolcka().modify(|w| w.set_lock(true)); // If we're disabled, we're done! let Some(fro16k) = self.config.fro16k.as_ref() else { return Ok(()); }; // Enabled, now set up. let Fro16KConfig { vsys_domain_active, vdd_core_domain_active, #[cfg(feature = "mcxa5xx")] vbat_domain_active, } = fro16k; // Enable clock outputs to both VSYS and VDD_CORE domains // Bit 0: clk_16k0 to VSYS domain // Bit 1: clk_16k1 to VDD_CORE/CORE_MAIN domain // Bit 2: clk_16k2 to VBAT domain (5xx only) // // TODO: Define sub-fields for this register with a PAC patch? let mut bits = 0; if *vsys_domain_active { bits |= 0b01; self.clocks.clk_16k_vsys = Some(Clock { frequency: 16_384, power: PoweredClock::AlwaysEnabled, }); } if *vdd_core_domain_active { bits |= 0b10; self.clocks.clk_16k_vdd_core = Some(Clock { frequency: 16_384, power: PoweredClock::AlwaysEnabled, }); } #[cfg(feature = "mcxa5xx")] if *vbat_domain_active { bits |= 0b100; self.clocks.clk_16k_vbat = Some(Clock { frequency: 16_384, power: PoweredClock::AlwaysEnabled, }); } self.vbat0.froclke().modify(|w| w.set_clke(bits)); Ok(()) } /// Configure the ROSC/OSC32K clock family #[cfg(all(feature = "mcxa5xx", feature = "unstable-osc32k", not(feature = "rosc-32k-as-gpio")))] pub(super) fn configure_osc32k_clocks(&mut self) -> Result<(), ClockError> { use config::{Osc32KCapSel, Osc32KCoarseGain, Osc32KMode}; use nxp_pac::vbat::vals::{ CoarseAmpGain, ExtalCapSel, InitTrim, ModeEn, StatusaLdoRdy, StatusaOscRdy, SupplyDet, XtalCapSel, }; // Unlock the control first self.vbat0.ldolcka().modify(|w| w.set_lock(false)); let Some(cfg) = self.config.osc32k.as_ref() else { // TODO: how to ensure disabled? // ??? // Re-lock after disabling self.vbat0.ldolcka().modify(|w| w.set_lock(true)); return Ok(()); }; // To enable and lock the LDO and bandgap: // // NOTE(AJM): "The FRO16K must be enabled before enabling the SRAM LDO or the bandgap" // // 1. Enable the FRO16K. // * NOTE(AJM): clk_16k is always enabled if enabled at all. // * TODO(AJM): I'm not sure which domain needs to be active for this requirement. // It seems reasonable that it would be the vbat domain? self.clocks.ensure_clk_16k_vbat_active(&PoweredClock::AlwaysEnabled)?; // 2. Write 7h to LDO_RAM Control A (LDOCTLA). self.vbat0.ldoctla().write(|w| { w.set_refresh_en(true); w.set_ldo_en(true); w.set_bg_en(true); }); // 3. Wait for STATUSA[LDO_RDY] to become 1. while self.vbat0.statusa().read().ldo_rdy() != StatusaLdoRdy::SET {} // 4. Write 1h to LDOLCKA[LOCK]. self.vbat0.ldolcka().modify(|w| w.set_lock(true)); match &cfg.mode { Osc32KMode::HighPower { coarse_amp_gain, xtal_cap_sel, extal_cap_sel, } => { // To configure and lock OSC32kHz for normal mode operation: // // 1. Configure OSCCTLA[EXTAL_CAP_SEL], OSCCTLA[XTAL_CAP_SEL] and OSCCTLA[COARSE_AMP_GAIN] as // required based on the external crystal component ESR and CL values, and by the PCB parasitics on the EXTAL32K and // XTAL32K pins. Configure 0h to OSCCTLA[MODE_EN], 1h to OSCCTLA[CAP_SEL_EN], and 1h to OSCCTLA[OSC_EN]. // * NOTE(AJM): You must write 1 to this field and OSCCTLA[OSC_EN] simultaneously. self.vbat0.oscctla().modify(|w| { w.set_xtal_cap_sel(match xtal_cap_sel { Osc32KCapSel::Cap2PicoF => XtalCapSel::SEL2, Osc32KCapSel::Cap4PicoF => XtalCapSel::SEL4, Osc32KCapSel::Cap6PicoF => XtalCapSel::SEL6, Osc32KCapSel::Cap8PicoF => XtalCapSel::SEL8, Osc32KCapSel::Cap10PicoF => XtalCapSel::SEL10, Osc32KCapSel::Cap12PicoF => XtalCapSel::SEL12, Osc32KCapSel::Cap14PicoF => XtalCapSel::SEL14, Osc32KCapSel::Cap16PicoF => XtalCapSel::SEL16, Osc32KCapSel::Cap18PicoF => XtalCapSel::SEL18, Osc32KCapSel::Cap20PicoF => XtalCapSel::SEL20, Osc32KCapSel::Cap22PicoF => XtalCapSel::SEL22, Osc32KCapSel::Cap24PicoF => XtalCapSel::SEL24, Osc32KCapSel::Cap26PicoF => XtalCapSel::SEL26, Osc32KCapSel::Cap28PicoF => XtalCapSel::SEL28, Osc32KCapSel::Cap30PicoF => XtalCapSel::SEL30, }); w.set_extal_cap_sel(match extal_cap_sel { Osc32KCapSel::Cap2PicoF => ExtalCapSel::SEL2, Osc32KCapSel::Cap4PicoF => ExtalCapSel::SEL4, Osc32KCapSel::Cap6PicoF => ExtalCapSel::SEL6, Osc32KCapSel::Cap8PicoF => ExtalCapSel::SEL8, Osc32KCapSel::Cap10PicoF => ExtalCapSel::SEL10, Osc32KCapSel::Cap12PicoF => ExtalCapSel::SEL12, Osc32KCapSel::Cap14PicoF => ExtalCapSel::SEL14, Osc32KCapSel::Cap16PicoF => ExtalCapSel::SEL16, Osc32KCapSel::Cap18PicoF => ExtalCapSel::SEL18, Osc32KCapSel::Cap20PicoF => ExtalCapSel::SEL20, Osc32KCapSel::Cap22PicoF => ExtalCapSel::SEL22, Osc32KCapSel::Cap24PicoF => ExtalCapSel::SEL24, Osc32KCapSel::Cap26PicoF => ExtalCapSel::SEL26, Osc32KCapSel::Cap28PicoF => ExtalCapSel::SEL28, Osc32KCapSel::Cap30PicoF => ExtalCapSel::SEL30, }); w.set_coarse_amp_gain(match coarse_amp_gain { Osc32KCoarseGain::EsrRange0 => CoarseAmpGain::GAIN05, Osc32KCoarseGain::EsrRange1 => CoarseAmpGain::GAIN10, Osc32KCoarseGain::EsrRange2 => CoarseAmpGain::GAIN18, Osc32KCoarseGain::EsrRange3 => CoarseAmpGain::GAIN33, }); w.set_mode_en(ModeEn::HP); w.set_cap_sel_en(true); w.set_osc_en(true); }); // 2. Wait for STATUSA[OSC_RDY] to become 1. while self.vbat0.statusa().read().osc_rdy() != StatusaOscRdy::SET {} // 3. Write 1h to OSCLCKA[LOCK]. self.vbat0.osclcka().modify(|w| w.set_lock(true)); // 4. Write 0h to OSCCTLA[EXTAL_CAP_SEL] and 0h to OSCCTLA[XTAL_CAP_SEL]. self.vbat0.oscctla().modify(|w| { w.set_xtal_cap_sel(XtalCapSel::SEL0); w.set_extal_cap_sel(ExtalCapSel::SEL0); }); // 5. Alter OSCCLKE[CLKE] to clock gate different OSC32K outputs to different peripherals to reduce power consumption. const ENABLED: Option = Some(Clock { frequency: 32_768, power: PoweredClock::NormalEnabledDeepSleepDisabled, }); self.vbat0.oscclke().modify(|w| { let mut val = 0u8; if cfg.vsys_domain_active { val |= 0b001; self.clocks.clk_32k_vsys = ENABLED; } if cfg.vdd_core_domain_active { val |= 0b010; self.clocks.clk_32k_vdd_core = ENABLED; } if cfg.vbat_domain_active { val |= 0b100; self.clocks.clk_32k_vbat = ENABLED; } w.set_clke(val); }); } Osc32KMode::LowPower { coarse_amp_gain, vbat_exceeds_3v0, } => { // To configure OSC32kHz for low power mode operation: // // 1. Write 3h to OSCCFGA[INIT_TRIM]. // * NOTE(AJM): This is "1 second"? self.vbat0.osccfga().modify(|w| w.set_init_trim(InitTrim::SEL3)); // 2. Configure OSCCTLA[EXTAL_CAP_SEL], OSCCTLA[XTAL_CAP_SEL] and OSCCTLA[COARSE_AMP_GAIN] as // required based on the external crystal component ESR and CL values, and by the PCB parasitics on the EXTAL32K and // XTAL32K pins. Configure 1h to OSCCTLA[MODE_EN], 1h to OSCCTLA[CAP_SEL_EN], and 1h to OSCCTLA[OSC_EN]. // * NOTE(AJM): The configuration EXTAL_CAP_SEL=0000 and CAP_SEL_EN=1 is required in low power // mode and is not supported in other modes self.vbat0.oscctla().modify(|w| { // TODO(AJM): Do we need to set these to reasonable values during the "startup" phase, and THEN // restore them to 0? RM is very unclear here. w.set_xtal_cap_sel(XtalCapSel::SEL0); w.set_extal_cap_sel(ExtalCapSel::SEL0); w.set_coarse_amp_gain(match coarse_amp_gain { Osc32KCoarseGain::EsrRange0 => CoarseAmpGain::GAIN05, Osc32KCoarseGain::EsrRange1 => CoarseAmpGain::GAIN10, Osc32KCoarseGain::EsrRange2 => CoarseAmpGain::GAIN18, Osc32KCoarseGain::EsrRange3 => CoarseAmpGain::GAIN33, }); // TODO: This naming is bad // // pub enum ModeEn { // #[doc = "Normal mode"] // HP = 0x0, // #[doc = "Startup mode"] // LP = 0x01, // _RESERVED_2 = 0x02, // #[doc = "Low power mode"] // SW = 0x03, // } w.set_mode_en(ModeEn::LP); w.set_cap_sel_en(true); w.set_osc_en(true); }); // 3. Wait for STATUSA[OSC_RDY] to become 1. while self.vbat0.statusa().read().osc_rdy() != StatusaOscRdy::SET {} // 4. Write 0h to OSCCFGA[INIT_TRIM]. self.vbat0.osccfga().modify(|w| w.set_init_trim(InitTrim::SEL0)); // 5. Configure 3h to OSCCTLA[MODE_EN], 0h to OSCCTLA[EXTAL_CAP_SEL] and 0h to OSCCTLA[XTAL_CAP_SEL]. // Configure OSCCTLA[SUPPLY_DET] as required by application. self.vbat0.oscctla().modify(|w| { w.set_mode_en(ModeEn::SW); w.set_xtal_cap_sel(XtalCapSel::SEL0); w.set_extal_cap_sel(ExtalCapSel::SEL0); w.set_supply_det(if *vbat_exceeds_3v0 { SupplyDet::G3VSUPPLY } else { SupplyDet::L3VSUPPLY }); }); // 6. Wait for STATUSA[OSC_RDY] to become 1. while self.vbat0.statusa().read().osc_rdy() != StatusaOscRdy::SET {} // 7. Alter OSCCLKE[CLKE] to clock gate different OSC32K outputs to different peripherals to reduce power consumption. const ENABLED: Option = Some(Clock { frequency: 32_768, power: PoweredClock::AlwaysEnabled, }); self.vbat0.oscclke().modify(|w| { let mut val = 0u8; if cfg.vsys_domain_active { val |= 0b001; self.clocks.clk_32k_vsys = ENABLED; } if cfg.vdd_core_domain_active { val |= 0b010; self.clocks.clk_32k_vdd_core = ENABLED; } if cfg.vbat_domain_active { val |= 0b100; self.clocks.clk_32k_vbat = ENABLED; } w.set_clke(val); }); } } Ok(()) } fn ensure_ldo_active(&mut self, for_clock: &'static str, for_power: &PoweredClock) -> Result<(), ClockError> { let bg_good = match for_power { PoweredClock::NormalEnabledDeepSleepDisabled => self.clocks.bandgap_active, PoweredClock::AlwaysEnabled => self.clocks.bandgap_active && self.clocks.bandgap_lowpower, }; if !bg_good { return Err(ClockError::BadConfig { clock: for_clock, reason: "LDO requires core bandgap enabled", }); } // TODO: Config for the LDO? For now, just enable // using the default settings: // LDOBYPASS: 0/not bypassed // VOUT_SEL: 0b100: 1.1v // LDOEN: 0/Disabled let already_enabled = { let ldocsr = self.scg0.ldocsr().read(); ldocsr.ldoen() && ldocsr.vout_ok() }; if !already_enabled { self.scg0.ldocsr().modify(|w| w.set_ldoen(true)); while !self.scg0.ldocsr().read().vout_ok() {} } Ok(()) } /// Configure the SOSC/clk_in oscillator #[cfg(not(feature = "sosc-as-gpio"))] pub(super) fn configure_sosc(&mut self) -> Result<(), ClockError> { let Some(parts) = self.config.sosc.as_ref() else { return Ok(()); }; // Enable (and wait for) LDO to be active self.ensure_ldo_active("sosc", &parts.power)?; let eref = match parts.mode { config::SoscMode::CrystalOscillator => Erefs::INTERNAL, config::SoscMode::ActiveClock => Erefs::EXTERNAL, }; let freq = parts.frequency; // TODO: Fix PAC names here // // #[doc = "0: Frequency range select of 8-16 MHz."] // Freq16to20mhz = 0, // #[doc = "1: Frequency range select of 16-25 MHz."] // LowFreq = 1, // #[doc = "2: Frequency range select of 25-40 MHz."] // MediumFreq = 2, // #[doc = "3: Frequency range select of 40-50 MHz."] // HighFreq = 3, let range = match freq { 0..8_000_000 => { return Err(ClockError::BadConfig { clock: "clk_in", reason: "freq too low", }); } 8_000_000..16_000_000 => Range::FREQ_16TO20MHZ, 16_000_000..25_000_000 => Range::LOW_FREQ, 25_000_000..40_000_000 => Range::MEDIUM_FREQ, 40_000_000..50_000_001 => Range::HIGH_FREQ, 50_000_001.. => { return Err(ClockError::BadConfig { clock: "clk_in", reason: "freq too high", }); } }; // Set source/erefs and range self.scg0.sosccfg().modify(|w| { w.set_erefs(eref); w.set_range(range); }); // Disable lock self.scg0.sosccsr().modify(|w| w.set_lk(SosccsrLk::WRITE_ENABLED)); // TODO: We could enable the SOSC clock monitor. There are some things to // figure out first: // // * This requires SIRC to be enabled, not sure which branch. Maybe fro12m_root? // * If SOSC needs to work in deep sleep, AND the monitor is enabled: // * SIRC also need needs to be low power // * We need to decide if we need an interrupt or a reset if the monitor trips let (bg_good, soscsten) = match parts.power { PoweredClock::NormalEnabledDeepSleepDisabled => (self.clocks.bandgap_active, false), PoweredClock::AlwaysEnabled => (self.clocks.bandgap_active && self.clocks.bandgap_lowpower, true), }; if !bg_good { return Err(ClockError::BadConfig { clock: "sosc", reason: "bandgap required", }); } // Apply remaining config self.scg0.sosccsr().modify(|w| { // For now, just disable the monitor. See above. w.set_sosccm(false); // Set deep sleep mode if needed w.set_soscsten(soscsten); // Enable SOSC w.set_soscen(true) }); // Wait for SOSC to be valid, check for errors while !self.scg0.sosccsr().read().soscvld() {} if self.scg0.sosccsr().read().soscerr() == Soscerr::ENABLED_AND_ERROR { return Err(ClockError::BadConfig { clock: "clk_in", reason: "soscerr is set", }); } // Re-lock the sosc self.scg0.sosccsr().modify(|w| w.set_lk(SosccsrLk::WRITE_DISABLED)); self.clocks.clk_in = Some(Clock { frequency: freq, power: parts.power, }); Ok(()) } pub(super) fn configure_spll(&mut self) -> Result<(), ClockError> { // # Vocab // // | Name | Meaning | // | :--- | :--- | // | Fin | Frequency of clkin | // | clkout | Output clock of the PLL | // | Fout | Frequency of clkout (depends on mode) | // | clkref | PLL Reference clock, the input clock to the PFD | // | Fref | Frequency of clkref, Fref = Fin / N | // | Fcco | Frequency of the output clock of the CCO, Fcco = M * Fref | // | N | Predivider value | // | M | Feedback divider value | // | P | Postdivider value | // | Tpon | PLL start-up time | // No PLL? Nothing to do! let Some(cfg) = self.config.spll.as_ref() else { return Ok(()); }; // Ensure the LDO is active self.ensure_ldo_active("spll", &cfg.power)?; // match on the source, ensure it is active already let res = match cfg.source { #[cfg(not(feature = "sosc-as-gpio"))] config::SpllSource::Sosc => self .clocks .clk_in .as_ref() .map(|c| (c, Source::SOSC)) .ok_or("sosc not active"), config::SpllSource::Firc => self .clocks .clk_hf_fundamental .as_ref() .map(|c| (c, Source::FIRC)) .ok_or("firc not active"), config::SpllSource::Sirc => self .clocks .fro_12m .as_ref() .map(|c| (c, Source::SIRC)) .ok_or("sirc not active"), }; // This checks if active let (clk, variant) = res.map_err(|s| ClockError::BadConfig { clock: "spll", reason: s, })?; // This checks the correct power reqs if !clk.power.meets_requirement_of(&cfg.power) { return Err(ClockError::BadConfig { clock: "spll", reason: "needs low power source", }); } // Bandwidth calc // // > In normal applications, you must calculate the bandwidth manually by using the feedback divider M (ranging from 1 to (2^16)-1), // > Equation 1, and Equation 2. The PLL is automatically stable in such case. In normal applications, SPLLCTRL[BANDDIRECT] must // > be 0; in this case, the bandwidth changes as a function of M. if clk.frequency == 0 { return Err(ClockError::BadConfig { clock: "spll", reason: "internal error", }); } // These are calculated differently depending on the mode. let f_in = clk.frequency; let bp_pre: bool; let bp_post: bool; let bp_post2: bool; let m: u16; let p: Option; let n: Option; // Calculate both Fout and Fcco so we can ensure they don't overflow // and are in range let fout: Option; let fcco: Option; let m_check = |m: u16| { if !(1..=u16::MAX).contains(&m) { Err(ClockError::BadConfig { clock: "spll", reason: "m_mult out of range", }) } else { Ok(m) } }; let p_check = |p: u8| { if !(1..=31).contains(&p) { Err(ClockError::BadConfig { clock: "spll", reason: "p_div out of range", }) } else { Ok(p) } }; let n_check = |n: u8| { if !(1..=u8::MAX).contains(&n) { Err(ClockError::BadConfig { clock: "spll", reason: "n_div out of range", }) } else { Ok(n) } }; match cfg.mode { // Fout = M x Fin config::SpllMode::Mode1a { m_mult } => { bp_pre = true; bp_post = true; bp_post2 = false; m = m_check(m_mult)?; p = None; n = None; fcco = f_in.checked_mul(m_mult as u32); fout = fcco; } // if !bypass_p2_div: Fout = (M / (2 x P)) x Fin // if bypass_p2_div: Fout = (M / P ) x Fin config::SpllMode::Mode1b { m_mult, p_div, bypass_p2_div, } => { bp_pre = true; bp_post = false; bp_post2 = bypass_p2_div; m = m_check(m_mult)?; p = Some(p_check(p_div)?); n = None; let mut div = p_div as u32; if !bypass_p2_div { div *= 2; } fcco = f_in.checked_mul(m_mult as u32); fout = (f_in / div).checked_mul(m_mult as u32); } // Fout = (M / N) x Fin config::SpllMode::Mode1c { m_mult, n_div } => { bp_pre = false; bp_post = true; bp_post2 = false; m = m_check(m_mult)?; p = None; n = Some(n_check(n_div)?); fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); fout = fcco; } // if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin // if bypass_p2_div: Fout = (M / ( N x P )) x Fin config::SpllMode::Mode1d { m_mult, n_div, p_div, bypass_p2_div, } => { bp_pre = false; bp_post = false; bp_post2 = bypass_p2_div; m = m_check(m_mult)?; p = Some(p_check(p_div)?); n = Some(n_check(n_div)?); // This can't overflow: u8 x u8 (x 2) always fits in u32 let mut div = (p_div as u32) * (n_div as u32); if !bypass_p2_div { div *= 2; } fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); fout = (f_in / div).checked_mul(m_mult as u32); } }; // Dump all the PLL calcs if needed for debugging #[cfg(feature = "defmt")] { defmt::debug!("f_in: {:?}", f_in); defmt::debug!("bp_pre: {:?}", bp_pre); defmt::debug!("bp_post: {:?}", bp_post); defmt::debug!("bp_post2: {:?}", bp_post2); defmt::debug!("m: {:?}", m); defmt::debug!("p: {:?}", p); defmt::debug!("n: {:?}", n); defmt::debug!("fout: {:?}", fout); defmt::debug!("fcco: {:?}", fcco); } // Ensure the Fcco and Fout calcs didn't overflow let fcco = fcco.ok_or(ClockError::BadConfig { clock: "spll", reason: "fcco invalid1", })?; let fout = fout.ok_or(ClockError::BadConfig { clock: "spll", reason: "fout invalid", })?; // Fcco: 275MHz to 550MHz if !(275_000_000..=550_000_000).contains(&fcco) { return Err(ClockError::BadConfig { clock: "spll", reason: "fcco invalid2", }); } let limits = self.lowest_relevant_limits(&cfg.power); // Fout: 4.3MHz to 2x Max CPU Frequency let fmax = limits.cpu_clk; let spll_range_bad1 = !(4_300_000..=(2 * fmax)).contains(&fout); let spll_range_bad2 = fout > limits.pll1_clk; if spll_range_bad1 || spll_range_bad2 { return Err(ClockError::BadConfig { clock: "spll", reason: "fout invalid", }); } // A = floor(m / 4) + 1 let selp_a = (m / 4) + 1; // SELP = A if A < 31 // = 31 if A >= 31 let selp = selp_a.min(31); // A = 1 if M >= 8000 // = floor(8000 / M) if 8000 > M >= 122 // = 2 x floor(M / 4) / 3 if 122 > M >= 1 let seli_a = if m >= 8000 { 1 } else if m >= 122 { 8000 / m } else { (2 * (m / 4)) / 3 }; // SELI = A if A < 63 // = 63 if A >= 63 let seli = seli_a.min(63); // SELR must be 0. let selr = 0; self.scg0.spllctrl().modify(|w| { w.set_source(variant); w.set_selp(selp as u8); w.set_seli(seli as u8); w.set_selr(selr); }); if let Some(n) = n { self.scg0.spllndiv().modify(|w| w.set_ndiv(n)); } if let Some(p) = p { self.scg0.spllpdiv().modify(|w| w.set_pdiv(p)); } self.scg0.spllmdiv().modify(|w| w.set_mdiv(m)); self.scg0.spllctrl().modify(|w| { w.set_bypassprediv(bp_pre); w.set_bypasspostdiv(bp_post); w.set_bypasspostdiv2(bp_post2); // TODO: support FRM? w.set_frm(false); }); // Unlock self.scg0.spllcsr().modify(|w| w.set_lk(SpllcsrLk::WRITE_ENABLED)); // TODO: Support clock monitors? // self.scg0.spllcsr().modify(|w| w.spllcm().?); self.scg0.trim_lock().write(|w| { w.set_trim_lock_key(0x5a5a); w.set_trim_unlock(TrimUnlock::NOT_LOCKED) }); // SPLLLOCK_CNFG: The lock time programmed in this register must be // equal to meet the PLL 500μs lock time plus the 300 refclk count startup. // // LOCK_TIME = 500μs/T ref + 300, F ref = F in /N (input frequency divided by pre-divider ratio). // // 500us is 1/2000th of a second, therefore Fref / 2000 is the number of cycles in 500us. let f_ref = if let Some(n) = n { f_in / (n as u32) } else { f_in }; let lock_time = f_ref.div_ceil(2000) + 300; self.scg0.splllock_cnfg().write(|w| w.set_lock_time(lock_time)); // TODO: Support Spread spectrum? let (bg_good, spllsten) = match cfg.power { PoweredClock::NormalEnabledDeepSleepDisabled => (self.clocks.bandgap_active, Spllsten::DISABLED_IN_STOP), PoweredClock::AlwaysEnabled => ( self.clocks.bandgap_active && self.clocks.bandgap_lowpower, Spllsten::ENABLED_IN_STOP, ), }; if !bg_good { return Err(ClockError::BadConfig { clock: "spll", reason: "bandgap required when active", }); } self.scg0.spllcsr().modify(|w| { w.set_spllclken(true); w.set_spllpwren(true); w.set_spllsten(spllsten); }); // Wait for SPLL to set up loop { let csr = self.scg0.spllcsr().read(); if csr.spll_lock() == SpllLock::ENABLED_AND_VALID { if csr.spllerr() == Spllerr::ENABLED_AND_ERROR { return Err(ClockError::BadConfig { clock: "spll", reason: "spllerr is set", }); } break; } } // Re-lock SPLL CSR self.scg0.spllcsr().modify(|w| w.set_lk(SpllcsrLk::WRITE_DISABLED)); // Store clock state self.clocks.pll1_clk = Some(Clock { frequency: fout, power: cfg.power, }); // Do we enable the `pll1_clk_div` output? if let Some(d) = cfg.pll1_clk_div.as_ref() { let exp_freq = fout / d.into_divisor(); if exp_freq > limits.pll1_clk_div { return Err(ClockError::BadConfig { clock: "pll1_clk_div", reason: "exceeds max frequency", }); } // Halt and reset the div; then set our desired div. self.syscon.pll1clkdiv().write(|w| { w.set_halt(Pll1clkdivHalt::HALT); w.set_reset(Pll1clkdivReset::ASSERTED); w.set_div(d.into_bits()); }); // Then unhalt it, and reset it self.syscon.pll1clkdiv().write(|w| { w.set_halt(Pll1clkdivHalt::RUN); w.set_reset(Pll1clkdivReset::RELEASED); }); // Wait for clock to stabilize while self.syscon.pll1clkdiv().read().unstab() == Pll1clkdivUnstab::ONGOING {} // Store off the clock info self.clocks.pll1_clk_div = Some(Clock { frequency: exp_freq, power: cfg.power, }); } Ok(()) } pub(super) fn configure_main_clk(&mut self) -> Result<(), ClockError> { let (var, name, clk) = match self.config.main_clock.source { #[cfg(not(feature = "sosc-as-gpio"))] MainClockSource::SoscClkIn => (Scs::SOSC, "clk_in", self.clocks.clk_in.as_ref()), MainClockSource::SircFro12M => (Scs::SIRC, "fro_12m", self.clocks.fro_12m.as_ref()), MainClockSource::FircHfRoot => (Scs::FIRC, "fro_hf_root", self.clocks.fro_hf_root.as_ref()), #[cfg(feature = "mcxa2xx")] MainClockSource::RoscFro16K => (Scs::ROSC, "fro16k", self.clocks.clk_16k_vdd_core.as_ref()), #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] MainClockSource::RoscOsc32K => (Scs::ROSC, "osc32k", self.clocks.clk_32k_vdd_core.as_ref()), MainClockSource::SPll1 => (Scs::SPLL, "pll1_clk", self.clocks.pll1_clk.as_ref()), }; let Some(main_clk_src) = clk else { return Err(ClockError::BadConfig { clock: name, reason: "Needed for main_clock but not enabled", }); }; if !main_clk_src.power.meets_requirement_of(&self.config.main_clock.power) { return Err(ClockError::BadConfig { clock: name, reason: "Needed for main_clock but not low power", }); } let lowest_limits = self.lowest_relevant_limits(&self.config.main_clock.power); let active_limits = self.active_limits(); let (levels, wsmax) = match self.config.vdd_power.active_mode.level { VddLevel::MidDriveMode => ( clock_limits::VDD_CORE_MID_DRIVE_WAIT_STATE_LIMITS, clock_limits::VDD_CORE_MID_DRIVE_MAX_WAIT_STATES, ), #[cfg(feature = "mcxa5xx")] VddLevel::NormalMode => ( clock_limits::VDD_CORE_NORMAL_DRIVE_WAIT_STATE_LIMITS, clock_limits::VDD_CORE_NORMAL_DRIVE_MAX_WAIT_STATES, ), VddLevel::OverDriveMode => ( clock_limits::VDD_CORE_OVER_DRIVE_WAIT_STATE_LIMITS, clock_limits::VDD_CORE_OVER_DRIVE_MAX_WAIT_STATES, ), }; // Is the main_clk source in range for main_clk? if main_clk_src.frequency > lowest_limits.main_clk { return Err(ClockError::BadConfig { clock: name, reason: "Exceeds main_clock frequency", }); } // Calculate expected CPU frequency based on main_clk and AHB div let ahb_div = self.config.main_clock.ahb_clk_div; let cpu_freq = main_clk_src.frequency / ahb_div.into_divisor(); // Is the expected CPU frequency in range for cpu_clk? Note: the CPU // is never running in deep sleep, so we directly use the active limits here if cpu_freq > active_limits.cpu_clk { return Err(ClockError::BadConfig { clock: name, reason: "Exceeds ahb max frequency", }); } // BEFORE we switch, update the flash wait states to the appropriate levels // // NOTE: "cpu_clk" is the same as "system_clk". Table 22 is not clear exactly // WHICH source clock the limits apply to, but system/ahb/cpu is a fair bet. // // TODO: This calculation doesn't consider low power mode yet! let wait_states = levels .iter() .find(|(fmax, _ws)| cpu_freq <= *fmax) .map(|t| t.1) .unwrap_or(wsmax); self.fmu0.fctrl().modify(|w| w.set_rwsc(wait_states)); // TODO: (Double) check if clock is actually valid before switching? // Are we already on the right clock? let now = self.scg0.csr().read().scs(); if now != var { // Set RCCR self.scg0.rccr().modify(|w| w.set_scs(var)); // Wait for match while self.scg0.csr().read().scs() != var {} } // The main_clk is now set to the selected input clock self.clocks.main_clk = Some(main_clk_src.clone()); // Update AHB clock division, if necessary if ahb_div.into_bits() != 0 { // AHB has no halt/reset fields - it's different to other DIV8s! self.syscon.ahbclkdiv().modify(|w| w.set_div(ahb_div.into_bits())); // Wait for clock to stabilize while self.syscon.ahbclkdiv().read().unstab() == AhbclkdivUnstab::ONGOING {} } // Store off the clock info self.clocks.cpu_system_clk = Some(Clock { frequency: cpu_freq, power: main_clk_src.power, }); Ok(()) } pub(super) fn configure_voltages(&mut self) -> Result<(), ClockError> { // Determine if we need to change the active mode voltage levels let to_change = match self.config.vdd_power.active_mode.level { VddLevel::MidDriveMode => { // This is the default mode, I don't believe we need to do anything. // // "The LVDE and HVDE fields reset only with a POR. // All other fields reset only with a system reset." None } #[cfg(feature = "mcxa5xx")] VddLevel::NormalMode => { // TODO: fix PAC fields, this is SRAM1V1 Some((ActiveCfgCoreldoVddLvl::NORMAL, Vsm::_RESERVED_2)) } VddLevel::OverDriveMode => Some((ActiveCfgCoreldoVddLvl::OVER, Vsm::SRAM1V2)), }; if let Some((vdd, vsm)) = to_change { // You can change the core VDD levels for the LDO_CORE low power regulator only // when CORELDO_VDD_DS=1. // // When switching CORELDO_VDD_DS from low to normal drive strength, ensure the LDO_CORE high // VDD LVL setting is set to the same level that was set prior to switching to the LDO_CORE drive strength // (CORELDO_VDD_DS). Otherwise, if the LVDs are enabled, an unexpected LVD can occur. // // Ensure drive strength is normal (BEFORE shifting level) self.spc0 .active_cfg() .modify(|w| w.set_coreldo_vdd_ds(ActiveCfgCoreldoVddDs::NORMAL)); // ## DS 26.3.2: // // When increasing voltage and frequency in Active mode, you must perform the following steps: // // 1. Increase voltage to a new level (ACTIVE_CFG[CORELDO_VDD_LVL]). self.spc0.active_cfg().modify(|w| w.set_coreldo_vdd_lvl(vdd)); // 2. Wait for voltage change to complete (SC[BUSY] = 0). while self.spc0.sc().read().busy() {} // 3. Configure flash memory to support higher voltage level and frequency (FMU_FCTRL[RWSC]. // // NOTE: This step skipped - we will update RWSC when we later apply main cpu clock // frequency changes. // 4. Configure SRAM to support higher voltage levels (SRAMCTL[VSM]). self.spc0.sramctl().modify(|w| w.set_vsm(vsm)); // 5. Request SRAM voltage update (write 1 to SRAMCTL[REQ]). self.spc0.sramctl().modify(|w| w.set_req(true)); // 6. Wait for SRAM voltage change to complete (SRAMCTL[ACK] = 1). while !self.spc0.sramctl().read().ack() {} // 7. Clear request for SRAM voltage change (write 0 to SRAMCTL[REQ]). self.spc0.sramctl().modify(|w| w.set_req(false)); // 8. Increase frequency to a new level (for example, SCG_RCCR). // // NOTE: This step skipped - we will update RCCR when we later apply main cpu clock // frequency changes. // 9. You can continue execution. // :) } // If the CORELDO_VDD_DS fields are set to the same value in both the ACTIVE_CFG and LP_CFG registers, // the CORELDO_VDD_LVL's in the ACTIVE_CFG and LP_CFG register must be set to the same voltage // level settings. // // TODO(AJM): I don't really understand this! Enforce it literally for now I guess. const BAD_ASCENDING: Result<(), ClockError> = Err(ClockError::BadConfig { clock: "vdd_power", reason: "Deep sleep can't have higher level than active mode", }); let ds_match = self.config.vdd_power.active_mode.drive == self.config.vdd_power.low_power_mode.drive; let (vdd_match, lpwkup) = match ( self.config.vdd_power.active_mode.level, self.config.vdd_power.low_power_mode.level, ) { // // Correct "descending" options // // When voltage levels are not the same between ACTIVE mode and Low Power mode, you must write a // nonzero value to SPC->LPWKUP_DELAY. // // This SHOULD be covered by table 165. LPWKUP Delay, but it doesn't actually have // a value for the 1.0v-1.2v transition we need. For now, the C SDK always uses 0x5B. #[cfg(feature = "mcxa5xx")] (VddLevel::OverDriveMode, VddLevel::NormalMode) => (false, 0x005b), (VddLevel::OverDriveMode, VddLevel::MidDriveMode) => (false, 0x005b), #[cfg(feature = "mcxa5xx")] (VddLevel::NormalMode, VddLevel::MidDriveMode) => (false, 0x005b), // // Incorrect "ascending" options // // For now, enforce that active is always >= voltage to low power. I don't know if this // is required, but there's probably also no reason to support it? #[cfg(feature = "mcxa5xx")] (VddLevel::MidDriveMode, VddLevel::NormalMode) => return BAD_ASCENDING, (VddLevel::MidDriveMode, VddLevel::OverDriveMode) => return BAD_ASCENDING, #[cfg(feature = "mcxa5xx")] (VddLevel::NormalMode, VddLevel::OverDriveMode) => return BAD_ASCENDING, // Correct "matching" options (VddLevel::MidDriveMode, VddLevel::MidDriveMode) => (true, 0x0000), #[cfg(feature = "mcxa5xx")] (VddLevel::NormalMode, VddLevel::NormalMode) => (true, 0x0000), (VddLevel::OverDriveMode, VddLevel::OverDriveMode) => (true, 0x0000), }; self.spc0.lpwkup_delay().write(|w| w.set_lpwkup_delay(lpwkup)); if ds_match && !vdd_match { return Err(ClockError::BadConfig { clock: "vdd_power", reason: "DS matches but LVL mismatches!", }); } // You can change the core VDD levels for the LDO_CORE low power regulator only when // ACTIVE_CFG[CORELDO_VDD_DS] = 1. So, before entering any of the low-power states (DSLEEP, // PDOWN, DPDOWN) with LDO_CORE low power regulator selected (LP_CFG[CORELDO_VDD_DS] = 0), // you must use CORELDO_VDD_LVL to select the correct regulation level during ACTIVE run mode. // // NOTE(AJM): We've set drive strength to "normal" above, and do not (potentially) set it to // "low" until later below. // NOTE(AJM): The reference manual doesn't have any similar configuration requirements // for low power mode. We'll just configure it, I guess? // // NOTE(AJM): "LP_CFG: This register resets only after a POR or LVD event." let (ds, bgap) = match self.config.vdd_power.low_power_mode.drive { VddDriveStrength::Low { enable_bandgap } => { // If the bandgap is enabled, also enable the high/low voltage // detectors. if it is disabled, these must also be disabled. self.spc0.lp_cfg().modify(|w| { w.set_sys_hvde(enable_bandgap); w.set_sys_lvde(enable_bandgap); w.set_core_lvde(enable_bandgap); }); (pac::spc::vals::LpCfgCoreldoVddDs::LOW, enable_bandgap) } VddDriveStrength::Normal => { // "If you specify normal drive strength, you must write a value to LP[BGMODE] that enables the bandgap." (pac::spc::vals::LpCfgCoreldoVddDs::NORMAL, true) } }; let lvl = match self.config.vdd_power.low_power_mode.level { VddLevel::MidDriveMode => LpCfgCoreldoVddLvl::MID, #[cfg(feature = "mcxa5xx")] VddLevel::NormalMode => LpCfgCoreldoVddLvl::NORMAL, VddLevel::OverDriveMode => LpCfgCoreldoVddLvl::OVER, }; self.spc0.lp_cfg().modify(|w| w.set_coreldo_vdd_ds(ds)); // If we're enabling the bandgap, ensure we do it BEFORE changing the VDD level // If we're disabling the bandgap, ensure we do it AFTER changing the VDD level if bgap { self.spc0.lp_cfg().modify(|w| w.set_bgmode(LpCfgBgmode::BGMODE01)); self.spc0.lp_cfg().modify(|w| w.set_coreldo_vdd_lvl(lvl)); } else { self.spc0.lp_cfg().modify(|w| w.set_coreldo_vdd_lvl(lvl)); self.spc0.lp_cfg().modify(|w| w.set_bgmode(LpCfgBgmode::BGMODE0)); } self.clocks.bandgap_lowpower = bgap; // Updating CORELDO_VDD_LVL sets the SC[BUSY] flag. That flag remains set for at least the total time // delay that Active Voltage Trim Delay (ACTIVE_VDELAY) specifies. // // Before changing CORELDO_VDD_LVL, you must wait until the SC[BUSY] flag clears before entering the // selected low-power sleep // // NOTE(AJM): Let's just proactively wait now so we don't have to worry about it on subsequent sleeps while self.spc0.sc().read().busy() {} // NOTE(AJM): I don't really know if this is valid! I'm guessing in most cases you would want to // use the low drive strength for lp mode, and high drive strength for active mode? match self.config.vdd_power.active_mode.drive { VddDriveStrength::Low { enable_bandgap } => { // If the bandgap is enabled, also enable the high/low voltage // detectors. if it is disabled, these must also be disabled. self.spc0.active_cfg().modify(|w| { w.set_sys_hvde(enable_bandgap); w.set_sys_lvde(enable_bandgap); w.set_core_lvde(enable_bandgap); }); // optionally disable bandgap AFTER setting vdd strength to low self.spc0 .active_cfg() .modify(|w| w.set_coreldo_vdd_ds(ActiveCfgCoreldoVddDs::LOW)); self.spc0.active_cfg().modify(|w| { if enable_bandgap { w.set_bgmode(ActiveCfgBgmode::BGMODE01) } else { w.set_bgmode(ActiveCfgBgmode::BGMODE0) } }); self.clocks.bandgap_active = enable_bandgap; } VddDriveStrength::Normal => { // Already set to normal above self.clocks.bandgap_active = true; } } // NOTE: calling `` still marks the core peripherals as taken. See // https://github.com/embassy-rs/embassy/issues/5563 for discussion. Since this // is a ZST, transmuting from `()` is reasonable. let mut scb: SCB = unsafe { core::mem::transmute(()) }; // Apply sleep settings match self.config.vdd_power.core_sleep { CoreSleep::WfeUngated => { // Do not gate self.cmc.ckctrl().modify(|w| w.set_ckmode(CkctrlCkmode::CKMODE0000)); // Debug is enabled when core sleeps self.cmc.dbgctl().modify(|w| w.set_sod(false)); // Don't allow the core to be gated to avoid killing the debugging session scb.clear_sleepdeep(); } CoreSleep::WfeGated => { // Allow automatic gating of the core when in LIGHT sleep self.cmc.ckctrl().modify(|w| w.set_ckmode(CkctrlCkmode::CKMODE0001)); // Debug is disabled when core sleeps self.cmc.dbgctl().modify(|w| w.set_sod(true)); // Allow the core to be gated - this WILL kill the debugging session! scb.set_sleepdeep(); } CoreSleep::DeepSleep => { // We can only support deep sleep with a custom executor which properly // handles going to sleep and returning #[cfg(all(not(feature = "executor-platform"), feature = "defmt"))] defmt::warn!("deep sleep enabled without custom executor"); // For now, just enable light sleep. The executor will set deep sleep when // appropriate self.cmc.ckctrl().modify(|w| w.set_ckmode(CkctrlCkmode::CKMODE0001)); // Debug is disabled when core sleeps self.cmc.dbgctl().modify(|w| w.set_sod(true)); // Allow the core to be gated - this WILL kill the debugging session! scb.set_sleepdeep(); // Enable sevonpend, to allow us to wake from WFE sleep with interrupts disabled unsafe { // TODO: wait for https://github.com/rust-embedded/cortex-m/commit/1be630fdd06990bd14251eabe4cca9307bde549d // to be released, until then, manual version of SCB.set_sevonpend(); scb.scr.modify(|w| w | (1 << 4)); } } } self.clocks.core_sleep = self.config.vdd_power.core_sleep; // Allow automatic gating of the flash memory let (wake, doze) = match self.config.vdd_power.flash_sleep { config::FlashSleep::Never => (false, false), config::FlashSleep::FlashDoze => (false, true), config::FlashSleep::FlashDozeWithFlashWake => (true, true), }; self.cmc.flashcr().modify(|w| { w.set_flashdoze(doze); w.set_flashwake(wake); }); // At init, disable all analog peripherals. These can be re-enabled // if necessary for HAL drivers. self.spc0.active_cfg1().write(|w| w.0 = 0); self.spc0.lp_cfg1().write(|w| w.0 = 0); // Update status self.clocks.active_power = self.config.vdd_power.active_mode.level; self.clocks.lp_power = self.config.vdd_power.low_power_mode.level; Ok(()) } } ================================================ FILE: embassy-mcxa/src/clocks/periph_helpers/mcxa2xx.rs ================================================ //! MCXA2xx only peripheral clocks helpers. ================================================ FILE: embassy-mcxa/src/clocks/periph_helpers/mcxa5xx.rs ================================================ //! MCXA5xx only peripheral clocks helpers. ================================================ FILE: embassy-mcxa/src/clocks/periph_helpers/mod.rs ================================================ //! Peripheral Helpers //! //! The purpose of this module is to define the per-peripheral special handling //! required from a clocking perspective. Different peripherals have different //! selectable source clocks, and some peripherals have additional pre-dividers //! that can be used. //! //! See the docs of [`SPConfHelper`] for more details. use super::{ClockError, Clocks, PoweredClock, WakeGuard}; use crate::clocks::VddLevel; use crate::pac::mrcc::vals::{ AdcClkselMux, ClkdivHalt, ClkdivReset, ClkdivUnstab, CtimerClkselMux, FclkClkselMux, Lpi2cClkselMux, LpspiClkselMux, LpuartClkselMux, OstimerClkselMux, }; #[cfg(feature = "mcxa2xx")] mod mcxa2xx; #[allow(unused_imports)] #[cfg(feature = "mcxa2xx")] pub use mcxa2xx::*; #[cfg(feature = "mcxa5xx")] mod mcxa5xx; #[allow(unused_imports)] #[cfg(feature = "mcxa5xx")] pub use mcxa5xx::*; #[must_use] pub struct PreEnableParts { /// The frequency fed into the peripheral, taking into account the selected /// source clock, as well as any pre-divisors. pub freq: u32, /// The wake guard, if necessary for the selected clock source pub wake_guard: Option, } impl PreEnableParts { pub fn empty() -> Self { Self { freq: 0, wake_guard: None, } } } /// Sealed Peripheral Configuration Helper /// /// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the /// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should /// fix the name, or actually do the sealing of peripherals. /// /// This trait serves to act as a per-peripheral customization for clocking behavior. /// /// This trait should be implemented on a configuration type for a given peripheral, and /// provide the methods that will be called by the higher level operations like /// `embassy_mcxa::clocks::enable_and_reset()`. pub trait SPConfHelper { /// This method is called AFTER a given MRCC peripheral has been disabled, and BEFORE /// the peripheral is to be enabled. /// /// This function SHOULD NOT make any changes to the system clock configuration, even /// unsafely, as this should remain static for the duration of the program. /// /// This function should check that any relevant upstream clocks are enabled, are in a /// reasonable power state, and that the requested configuration can be made. If any of /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`. /// /// This function WILL be called in a critical section, care should be taken not to delay /// for an unreasonable amount of time. /// /// On success, this function MUST return an `Ok(parts)`. fn pre_enable_config(&self, clocks: &Clocks) -> Result; } /// Copy and paste macro that: /// /// * Sets the clocksel mux to `$selvar` /// * Resets and halts the div, and applies the calculated div4 bits /// * Releases reset + halt /// * Waits for the div to stabilize /// * Returns `Ok($freq / $conf.div.into_divisor())` /// /// Assumes: /// /// * self is a configuration struct that has fields called: /// * `div`, which is a `Div4` /// * `power`, which is a `PoweredClock` /// /// usage: /// /// ```rust,ignore /// apply_div4!(self, clksel, clkdiv, variant, freq) /// ``` /// /// In the future if we make all the clksel+clkdiv pairs into commonly derivedFrom /// registers, or if we put some kind of simple trait around those regs, we could /// do this with something other than a macro, but for now, this is harm-reduction /// to avoid incorrect copy + paste #[doc(hidden)] #[macro_export] macro_rules! apply_div4 { ($conf:ident, $selreg:ident, $divreg:ident, $selvar:ident, $freq:ident) => {{ // set clksel $selreg.modify(|w| w.set_mux($selvar)); // Set up clkdiv $divreg.modify(|w| { w.set_div($conf.div.into_bits()); w.set_halt(ClkdivHalt::OFF); w.set_reset(ClkdivReset::OFF); }); $divreg.modify(|w| { w.set_halt(ClkdivHalt::ON); w.set_reset(ClkdivReset::ON); }); while $divreg.read().unstab() == ClkdivUnstab::OFF {} Ok(PreEnableParts { freq: $freq / $conf.div.into_divisor(), wake_guard: WakeGuard::for_power(&$conf.power), }) }}; } // config types /// This type represents a divider in the range 1..=16. /// /// At a hardware level, this is an 8-bit register from 0..=15, /// which adds one. /// /// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain /// seems to use 4 bit dividers! #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Div4(pub(super) u8); impl Div4 { /// Divide by one, or no division pub const fn no_div() -> Self { Self(0) } /// Store a "raw" divisor value that will divide the source by /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source /// by 1, and `Div4::from_raw(15)` will divide the source by /// 16. pub const fn from_raw(n: u8) -> Option { if n > 0b1111 { None } else { Some(Self(n)) } } /// Store a specific divisor value that will divide the source /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source /// by 1, and `Div4::from_divisor(16)` will divide the source /// by 16. /// /// Will return `None` if `n` is not in the range `1..=16`. /// Consider [`Self::from_raw`] for an infallible version. pub const fn from_divisor(n: u8) -> Option { let Some(n) = n.checked_sub(1) else { return None; }; if n > 0b1111 { return None; } Some(Self(n)) } /// Convert into "raw" bits form #[inline(always)] pub const fn into_bits(self) -> u8 { self.0 } /// Convert into "divisor" form, as a u32 for convenient frequency math #[inline(always)] pub const fn into_divisor(self) -> u32 { self.0 as u32 + 1 } } /// A basic type that always returns an error when `post_enable_config` is called. /// /// Should only be used as a placeholder. pub struct UnimplementedConfig; impl SPConfHelper for UnimplementedConfig { fn pre_enable_config(&self, _clocks: &Clocks) -> Result { Err(ClockError::UnimplementedConfig) } } /// A basic type that always returns `Ok` when `PreEnableParts` is called. /// /// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` /// peripherals, which have no selectable/configurable source clock. pub struct NoConfig; impl SPConfHelper for NoConfig { fn pre_enable_config(&self, _clocks: &Clocks) -> Result { Ok(PreEnableParts::empty()) } } /// A basic type that always returns `Ok` when `PreEnableParts` is called. /// /// This should only be used for peripherals that are clocked only by /// the CLK1M clock and have no other selectable/configurable source /// clock. pub struct Clk1MConfig; impl SPConfHelper for Clk1MConfig { fn pre_enable_config(&self, _clocks: &Clocks) -> Result { Ok(PreEnableParts { freq: 1_000_000, wake_guard: None, }) } } // // Adc // /// Selectable clocks for the ADC peripheral #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AdcClockSel { /// Divided `fro_lf`/`clk_12m`/FRO12M source FroLfDiv, /// Gated `fro_hf`/`FRO180M` source FroHf, /// External Clock Source #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, // /// USB PLL Clk // #[cfg(feature = "mcxa5xx")] // UsbPllClk, /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` Clk1M, /// Internal PLL output, with configurable divisor Pll1ClkDiv, /// No clock/disabled None, } /// Top level configuration for the ADC peripheral pub struct AdcConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Selected clock-source for this peripheral pub source: AdcClockSel, /// Pre-divisor, applied to the upstream clock output pub div: Div4, } impl SPConfHelper for AdcConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { let mrcc0 = crate::pac::MRCC0; let (freq, variant) = match self.source { AdcClockSel::FroLfDiv => { let freq = clocks.ensure_fro_lf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = AdcClkselMux::CLKROOT_FUNC_0; #[cfg(feature = "mcxa5xx")] let mux = AdcClkselMux::I0_CLKROOT_SIRC_DIV; (freq, mux) } AdcClockSel::FroHf => { let freq = clocks.ensure_fro_hf_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = AdcClkselMux::CLKROOT_FUNC_1; #[cfg(feature = "mcxa5xx")] let mux = AdcClkselMux::I1_CLKROOT_FIRC_GATED; (freq, mux) } #[cfg(not(feature = "sosc-as-gpio"))] AdcClockSel::ClkIn => { let freq = clocks.ensure_clk_in_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = AdcClkselMux::CLKROOT_FUNC_3; #[cfg(feature = "mcxa5xx")] let mux = AdcClkselMux::I3_CLKROOT_SOSC; (freq, mux) } // #[cfg(feature = "mcxa5xx")] // AdcClockSel::UsbPllClk => { // let freq = clocks.ensure_usb_pll_clk_active(&self.power)?; // let mux = AdcClkselMux::I4_CLKROOT_USBPFD; // (freq, mux) // } AdcClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = AdcClkselMux::CLKROOT_FUNC_5; #[cfg(feature = "mcxa5xx")] let mux = AdcClkselMux::I5_CLKROOT_1M; (freq, mux) } AdcClockSel::Pll1ClkDiv => { let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = AdcClkselMux::CLKROOT_FUNC_6; #[cfg(feature = "mcxa5xx")] let mux = AdcClkselMux::I6_CLKROOT_SPLL_DIV; (freq, mux) } AdcClockSel::None => { mrcc0.mrcc_adc_clksel().write(|w| { // no ClkrootFunc7, just write manually for now w.set_mux(AdcClkselMux::_RESERVED_7) }); mrcc0.mrcc_adc_clkdiv().modify(|w| { w.set_reset(ClkdivReset::ON); w.set_halt(ClkdivHalt::ON); }); return Ok(PreEnableParts::empty()); } }; let clksel = mrcc0.mrcc_adc_clksel(); let clkdiv = mrcc0.mrcc_adc_clkdiv(); // Check clock speed is reasonable let div = self.div.into_divisor(); let expected = freq / div; // 22.3.2 peripheral clock max functional clock limits let power = match self.power { PoweredClock::NormalEnabledDeepSleepDisabled => clocks.active_power, PoweredClock::AlwaysEnabled => clocks.lp_power, }; #[cfg(feature = "mcxa2xx")] let fmax = match power { VddLevel::MidDriveMode => 24_000_000, VddLevel::OverDriveMode => 64_000_000, }; #[cfg(feature = "mcxa5xx")] let fmax = match power { VddLevel::MidDriveMode => 24_000_000, VddLevel::NormalMode | VddLevel::OverDriveMode => 64_000_000, }; if expected > fmax { return Err(ClockError::BadConfig { clock: "adc fclk", reason: "exceeds max rating", }); } apply_div4!(self, clksel, clkdiv, variant, freq) } } // // OSTimer // /// Selectable clocks for the OSTimer peripheral #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum OstimerClockSel { /// 16k clock, sourced from FRO16K (Vdd Core) Clk16kVddCore, /// 1 MHz Clock sourced from FRO12M Clk1M, /// Disabled None, } /// Top level configuration for the `OSTimer` peripheral pub struct OsTimerConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Selected clock source for this peripheral pub source: OstimerClockSel, } impl SPConfHelper for OsTimerConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { let mrcc0 = crate::pac::MRCC0; // NOTE: complies with 22.3.2 peripheral clock max functional clock limits // which is 1MHz, and we can only select 1mhz/16khz. Ok(match self.source { OstimerClockSel::Clk16kVddCore => { // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = OstimerClkselMux::CLKROOT_16K; #[cfg(feature = "mcxa5xx")] let mux = OstimerClkselMux::I0_CLKROOT_16K; let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; mrcc0.mrcc_ostimer0_clksel().write(|w| w.set_mux(mux)); PreEnableParts { freq, wake_guard: WakeGuard::for_power(&self.power), } } OstimerClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = OstimerClkselMux::CLKROOT_1M; #[cfg(feature = "mcxa5xx")] let mux = OstimerClkselMux::I2_CLKROOT_1M; mrcc0.mrcc_ostimer0_clksel().write(|w| w.set_mux(mux)); PreEnableParts { freq, wake_guard: WakeGuard::for_power(&self.power), } } OstimerClockSel::None => { mrcc0 .mrcc_ostimer0_clksel() .write(|w| w.set_mux(OstimerClkselMux::_RESERVED_3)); PreEnableParts::empty() } }) } } // // LPSPI // /// Selectable clocks for `Lpspi` peripherals #[derive(Debug, Clone, Copy)] pub enum LpspiClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider /// "fro_lf_div" FroLfDiv, /// FRO180M/FRO192M/FRO_HF/FIRC clock source, passed through divider /// "fro_hf_div" FroHfDiv, /// SOSC/XTAL/EXTAL clock source #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, /// clk_1m/FRO_LF divided by 12 Clk1M, /// Output of PLL1, passed through clock divider, /// "pll1_clk_div", maybe "pll1_lf_div"? Pll1ClkDiv, /// Disabled None, } /// Which instance of the `Lpspi` is this? /// /// Should not be directly selectable by end-users. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LpspiInstance { /// Instance 0 Lpspi0, /// Instance 1 Lpspi1, /// Instance 2 #[cfg(feature = "mcxa5xx")] Lpspi2, /// Instance 3 #[cfg(feature = "mcxa5xx")] Lpspi3, /// Instance 4 #[cfg(feature = "mcxa5xx")] Lpspi4, /// Instance 5 #[cfg(feature = "mcxa5xx")] Lpspi5, } /// Top level configuration for `Lpspi` instances. pub struct LpspiConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: LpspiClockSel, /// Clock divisor pub div: Div4, /// Which instance is this? // NOTE: should not be user settable pub(crate) instance: LpspiInstance, } impl SPConfHelper for LpspiConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { // check that source is suitable let mrcc0 = crate::pac::MRCC0; let (clkdiv, clksel) = match self.instance { LpspiInstance::Lpspi0 => (mrcc0.mrcc_lpspi0_clkdiv(), mrcc0.mrcc_lpspi0_clksel()), LpspiInstance::Lpspi1 => (mrcc0.mrcc_lpspi1_clkdiv(), mrcc0.mrcc_lpspi1_clksel()), #[cfg(feature = "mcxa5xx")] LpspiInstance::Lpspi2 => (mrcc0.mrcc_lpspi2_clkdiv(), mrcc0.mrcc_lpspi2_clksel()), #[cfg(feature = "mcxa5xx")] LpspiInstance::Lpspi3 => (mrcc0.mrcc_lpspi3_clkdiv(), mrcc0.mrcc_lpspi3_clksel()), #[cfg(feature = "mcxa5xx")] LpspiInstance::Lpspi4 => (mrcc0.mrcc_lpspi4_clkdiv(), mrcc0.mrcc_lpspi4_clksel()), #[cfg(feature = "mcxa5xx")] LpspiInstance::Lpspi5 => (mrcc0.mrcc_lpspi5_clkdiv(), mrcc0.mrcc_lpspi5_clksel()), }; let (freq, variant) = match self.source { LpspiClockSel::FroLfDiv => { let freq = clocks.ensure_fro_lf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpspiClkselMux::CLKROOT_FUNC_0; #[cfg(feature = "mcxa5xx")] let mux = LpspiClkselMux::I0_CLKROOT_FUNC_0; (freq, mux) } LpspiClockSel::FroHfDiv => { let freq = clocks.ensure_fro_hf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpspiClkselMux::CLKROOT_FUNC_2; #[cfg(feature = "mcxa5xx")] let mux = LpspiClkselMux::I2_CLKROOT_FUNC_2; (freq, mux) } #[cfg(not(feature = "sosc-as-gpio"))] LpspiClockSel::ClkIn => { let freq = clocks.ensure_clk_in_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpspiClkselMux::CLKROOT_FUNC_3; #[cfg(feature = "mcxa5xx")] let mux = LpspiClkselMux::I3_CLKROOT_FUNC_3; (freq, mux) } LpspiClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpspiClkselMux::CLKROOT_FUNC_5; #[cfg(feature = "mcxa5xx")] let mux = LpspiClkselMux::I5_CLKROOT_FUNC_5; (freq, mux) } LpspiClockSel::Pll1ClkDiv => { let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpspiClkselMux::CLKROOT_FUNC_6; #[cfg(feature = "mcxa5xx")] let mux = LpspiClkselMux::I6_CLKROOT_FUNC_6; (freq, mux) } LpspiClockSel::None => { // no ClkrootFunc7, just write manually for now clksel.write(|w| w.0 = 0b111); clkdiv.modify(|w| { w.set_reset(ClkdivReset::OFF); w.set_halt(ClkdivHalt::OFF); }); return Ok(PreEnableParts::empty()); } }; let div = self.div.into_divisor(); let expected = freq / div; // 21.3.2 peripheral clock max functional clock limits let power = match self.power { PoweredClock::NormalEnabledDeepSleepDisabled => clocks.active_power, PoweredClock::AlwaysEnabled => clocks.lp_power, }; #[cfg(feature = "mcxa2xx")] let fmax = match power { VddLevel::MidDriveMode => 25_000_000, VddLevel::OverDriveMode => 60_000_000, }; #[cfg(feature = "mcxa5xx")] let fmax = match power { VddLevel::MidDriveMode => 50_000_000, VddLevel::NormalMode => 150_000_000, VddLevel::OverDriveMode => 200_000_000, }; if expected > fmax { return Err(ClockError::BadConfig { clock: "lpspi fclk", reason: "exceeds max rating", }); } apply_div4!(self, clksel, clkdiv, variant, freq) } } // // I3C // /// Selectable clocks for `I3c` peripherals #[derive(Debug, Clone, Copy)] pub enum I3cClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider /// "fro_lf_div" FroLfDiv, /// FRO180M/FRO_HF/FIRC clock source, passed through divider /// "fro_hf_div" FroHfDiv, /// SOSC/XTAL/EXTAL clock source #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, /// clk_1m/FRO_LF divided by 12 Clk1M, /// Internal PLL output, with configurable divisor #[cfg(feature = "mcxa5xx")] Pll1ClkDiv, /// Disabled None, } /// Top level configuration for `I3c` instances. pub struct I3cConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: I3cClockSel, /// Clock divisor pub div: Div4, } impl SPConfHelper for I3cConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { #[cfg(feature = "mcxa2xx")] // Always 25MHz maximum frequency. const I3C_FCLK_MAX: u32 = 25_000_000; #[cfg(feature = "mcxa5xx")] // Always 100MHz maximum frequency. const I3C_FCLK_MAX: u32 = 100_000_000; // check that source is suitable let mrcc0 = crate::pac::MRCC0; let (clkdiv, clksel) = (mrcc0.mrcc_i3c0_fclk_clkdiv(), mrcc0.mrcc_i3c0_fclk_clksel()); let (freq, variant) = match self.source { I3cClockSel::FroLfDiv => { let freq = clocks.ensure_fro_lf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = FclkClkselMux::CLKROOT_FUNC_0; #[cfg(feature = "mcxa5xx")] let mux = FclkClkselMux::I0_CLKROOT_FUNC_0; (freq, mux) } I3cClockSel::FroHfDiv => { let freq = clocks.ensure_fro_hf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = FclkClkselMux::CLKROOT_FUNC_2; #[cfg(feature = "mcxa5xx")] let mux = FclkClkselMux::I2_CLKROOT_FUNC_2; (freq, mux) } #[cfg(not(feature = "sosc-as-gpio"))] I3cClockSel::ClkIn => { let freq = clocks.ensure_clk_in_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = FclkClkselMux::CLKROOT_FUNC_3; #[cfg(feature = "mcxa5xx")] let mux = FclkClkselMux::I3_CLKROOT_FUNC_3; (freq, mux) } I3cClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = FclkClkselMux::CLKROOT_FUNC_5; #[cfg(feature = "mcxa5xx")] let mux = FclkClkselMux::I5_CLKROOT_FUNC_5; (freq, mux) } #[cfg(feature = "mcxa5xx")] I3cClockSel::Pll1ClkDiv => { let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = FclkClkselMux::CLKROOT_FUNC_6; #[cfg(feature = "mcxa5xx")] let mux = FclkClkselMux::I6_CLKROOT_FUNC_6; (freq, mux) } I3cClockSel::None => { // no ClkrootFunc7, just write manually for now clksel.write(|w| w.0 = 0b111); clkdiv.modify(|w| { w.set_reset(ClkdivReset::OFF); w.set_halt(ClkdivHalt::OFF); }); return Ok(PreEnableParts::empty()); } }; if freq > I3C_FCLK_MAX { return Err(ClockError::BadConfig { clock: "i3c fclk", reason: "exceeds max rating", }); } apply_div4!(self, clksel, clkdiv, variant, freq) } } // // LPI2c // /// Selectable clocks for `Lpi2c` peripherals #[derive(Debug, Clone, Copy)] pub enum Lpi2cClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider /// "fro_lf_div" FroLfDiv, /// FRO180M/FRO192M/FRO_HF/FIRC clock source, passed through divider /// "fro_hf_div" FroHfDiv, /// SOSC/XTAL/EXTAL clock source #[cfg(feature = "mcxa2xx")] #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, /// clk_1m/FRO_LF divided by 12 Clk1M, /// Output of PLL1, passed through clock divider, /// "pll1_clk_div", maybe "pll1_lf_div"? #[cfg(feature = "mcxa2xx")] Pll1ClkDiv, /// Disabled None, } /// Which instance of the `Lpi2c` is this? /// /// Should not be directly selectable by end-users. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Lpi2cInstance { /// Instance 0 Lpi2c0, /// Instance 1 Lpi2c1, /// Instance 2 Lpi2c2, /// Instance 3 Lpi2c3, } /// Top level configuration for `Lpi2c` instances. pub struct Lpi2cConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: Lpi2cClockSel, /// Clock divisor pub div: Div4, /// Which instance is this? // NOTE: should not be user settable pub(crate) instance: Lpi2cInstance, } impl SPConfHelper for Lpi2cConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { // check that source is suitable let mrcc0 = crate::pac::MRCC0; let (clkdiv, clksel) = match self.instance { Lpi2cInstance::Lpi2c0 => (mrcc0.mrcc_lpi2c0_clkdiv(), mrcc0.mrcc_lpi2c0_clksel()), Lpi2cInstance::Lpi2c1 => (mrcc0.mrcc_lpi2c1_clkdiv(), mrcc0.mrcc_lpi2c1_clksel()), Lpi2cInstance::Lpi2c2 => (mrcc0.mrcc_lpi2c2_clkdiv(), mrcc0.mrcc_lpi2c2_clksel()), Lpi2cInstance::Lpi2c3 => (mrcc0.mrcc_lpi2c3_clkdiv(), mrcc0.mrcc_lpi2c3_clksel()), }; let (freq, variant) = match self.source { Lpi2cClockSel::FroLfDiv => { let freq = clocks.ensure_fro_lf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = Lpi2cClkselMux::CLKROOT_FUNC_0; #[cfg(feature = "mcxa5xx")] let mux = Lpi2cClkselMux::I0_CLKROOT_FUNC_0; (freq, mux) } Lpi2cClockSel::FroHfDiv => { let freq = clocks.ensure_fro_hf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = Lpi2cClkselMux::CLKROOT_FUNC_2; #[cfg(feature = "mcxa5xx")] let mux = Lpi2cClkselMux::I2_CLKROOT_FUNC_2; (freq, mux) } #[cfg(feature = "mcxa2xx")] #[cfg(not(feature = "sosc-as-gpio"))] Lpi2cClockSel::ClkIn => { let freq = clocks.ensure_clk_in_active(&self.power)?; (freq, Lpi2cClkselMux::CLKROOT_FUNC_3) } Lpi2cClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = Lpi2cClkselMux::CLKROOT_FUNC_5; #[cfg(feature = "mcxa5xx")] let mux = Lpi2cClkselMux::I5_CLKROOT_FUNC_5; (freq, mux) } #[cfg(feature = "mcxa2xx")] Lpi2cClockSel::Pll1ClkDiv => { let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; (freq, Lpi2cClkselMux::CLKROOT_FUNC_6) } Lpi2cClockSel::None => { // no ClkrootFunc7, just write manually for now clksel.write(|w| w.0 = 0b111); clkdiv.modify(|w| { w.set_reset(ClkdivReset::OFF); w.set_halt(ClkdivHalt::OFF); }); return Ok(PreEnableParts::empty()); } }; let div = self.div.into_divisor(); let expected = freq / div; // 22.3.2 peripheral clock max functional clock limits let power = match self.power { PoweredClock::NormalEnabledDeepSleepDisabled => clocks.active_power, PoweredClock::AlwaysEnabled => clocks.lp_power, }; #[cfg(feature = "mcxa2xx")] let fmax = match power { VddLevel::MidDriveMode => 25_000_000, VddLevel::OverDriveMode => 60_000_000, }; #[cfg(feature = "mcxa5xx")] let fmax = match power { VddLevel::MidDriveMode => 25_000_000, VddLevel::NormalMode | VddLevel::OverDriveMode => 64_000_000, }; if expected > fmax { return Err(ClockError::BadConfig { clock: "lpi2c fclk", reason: "exceeds max rating", }); } apply_div4!(self, clksel, clkdiv, variant, freq) } } // // LPUart // /// Selectable clocks for Lpuart peripherals #[derive(Debug, Clone, Copy)] pub enum LpuartClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider /// "fro_lf_div" FroLfDiv, /// FRO180M/FRO192M/FRO_HF/FIRC clock source, passed through divider /// "fro_hf_div" FroHfDiv, /// SOSC/XTAL/EXTAL clock source #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, /// FRO16K/clk_16k source #[cfg(feature = "mcxa2xx")] Clk16K, /// clk_1m/FRO_LF divided by 12 Clk1M, /// Output of PLL1, passed through clock divider, /// "pll1_clk_div", maybe "pll1_lf_div"? Pll1ClkDiv, /// Disabled None, } /// Which instance of the Lpuart is this? /// /// Should not be directly selectable by end-users. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LpuartInstance { /// Instance 0 Lpuart0, /// Instance 1 Lpuart1, /// Instance 2 Lpuart2, /// Instance 3 Lpuart3, /// Instance 4 Lpuart4, /// Instance 5 Lpuart5, } /// Top level configuration for `Lpuart` instances. pub struct LpuartConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: LpuartClockSel, /// Clock divisor pub div: Div4, /// Which instance is this? // NOTE: should not be user settable pub(crate) instance: LpuartInstance, } impl SPConfHelper for LpuartConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { // check that source is suitable let mrcc0 = crate::pac::MRCC0; let (clkdiv, clksel) = match self.instance { LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), }; let (freq, variant) = match self.source { LpuartClockSel::FroLfDiv => { let freq = clocks.ensure_fro_lf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpuartClkselMux::CLKROOT_FUNC_0; #[cfg(feature = "mcxa5xx")] let mux = LpuartClkselMux::I0_CLKROOT_SIRC_DIV; (freq, mux) } LpuartClockSel::FroHfDiv => { let freq = clocks.ensure_fro_hf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpuartClkselMux::CLKROOT_FUNC_2; #[cfg(feature = "mcxa5xx")] let mux = LpuartClkselMux::I2_CLKROOT_FIRC_DIV; (freq, mux) } #[cfg(not(feature = "sosc-as-gpio"))] LpuartClockSel::ClkIn => { let freq = clocks.ensure_clk_in_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpuartClkselMux::CLKROOT_FUNC_3; #[cfg(feature = "mcxa5xx")] let mux = LpuartClkselMux::I3_CLKROOT_SOSC; (freq, mux) } #[cfg(feature = "mcxa2xx")] LpuartClockSel::Clk16K => { let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpuartClkselMux::CLKROOT_FUNC_4; // #[cfg(feature = "mcxa5xx")] // let mux = LpuartClkselMux::I4_CLKROOT_LPOSC; (freq, mux) } LpuartClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpuartClkselMux::CLKROOT_FUNC_5; #[cfg(feature = "mcxa5xx")] let mux = LpuartClkselMux::I5_CLKROOT_1M; (freq, mux) } LpuartClockSel::Pll1ClkDiv => { let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = LpuartClkselMux::CLKROOT_FUNC_6; #[cfg(feature = "mcxa5xx")] let mux = LpuartClkselMux::I6_CLKROOT_SPLL_DIV; (freq, mux) } LpuartClockSel::None => { // no ClkrootFunc7, just write manually for now clksel.write(|w| w.set_mux(LpuartClkselMux::_RESERVED_7)); clkdiv.modify(|w| { w.set_reset(ClkdivReset::ON); w.set_halt(ClkdivHalt::ON); }); return Ok(PreEnableParts::empty()); } }; // Check clock speed is reasonable let div = self.div.into_divisor(); let expected = freq / div; // 22.3.2 peripheral clock max functional clock limits let power = match self.power { PoweredClock::NormalEnabledDeepSleepDisabled => clocks.active_power, PoweredClock::AlwaysEnabled => clocks.lp_power, }; #[cfg(feature = "mcxa2xx")] let fmax = match power { VddLevel::MidDriveMode => 45_000_000, VddLevel::OverDriveMode => 180_000_000, }; #[cfg(feature = "mcxa5xx")] let fmax = match power { VddLevel::MidDriveMode => 50_000_000, VddLevel::NormalMode => 150_000_000, VddLevel::OverDriveMode => 200_000_000, }; if expected > fmax { return Err(ClockError::BadConfig { clock: "lpuart fclk", reason: "exceeds max rating", }); } // set clksel apply_div4!(self, clksel, clkdiv, variant, freq) } } // // CTimer // /// Selectable clocks for `CTimer` peripherals #[derive(Debug, Clone, Copy)] pub enum CTimerClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider /// "fro_lf_div" FroLfDiv, /// FRO180M/FRO192M/FRO_HF/FIRC clock source, passed through divider /// "fro_hf_div" FroHfDiv, /// SOSC/XTAL/EXTAL clock source #[cfg(not(feature = "sosc-as-gpio"))] ClkIn, /// FRO16K/clk_16k source #[cfg(feature = "mcxa2xx")] Clk16K, /// clk_1m/FRO_LF divided by 12 Clk1M, /// Internal PLL output, with configurable divisor Pll1ClkDiv, /// Disabled None, } /// Which instance of the `CTimer` is this? /// /// Should not be directly selectable by end-users. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum CTimerInstance { /// Instance 0 CTimer0, /// Instance 1 CTimer1, /// Instance 2 CTimer2, /// Instance 3 CTimer3, /// Instance 4 CTimer4, } /// Top level configuration for `CTimer` instances. pub struct CTimerConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: CTimerClockSel, /// Clock divisor pub div: Div4, /// Which instance is this? // NOTE: should not be user settable pub(crate) instance: CTimerInstance, } impl SPConfHelper for CTimerConfig { fn pre_enable_config(&self, clocks: &Clocks) -> Result { // check that source is suitable let mrcc0 = crate::pac::MRCC0; let (clkdiv, clksel) = match self.instance { CTimerInstance::CTimer0 => (mrcc0.mrcc_ctimer0_clkdiv(), mrcc0.mrcc_ctimer0_clksel()), CTimerInstance::CTimer1 => (mrcc0.mrcc_ctimer1_clkdiv(), mrcc0.mrcc_ctimer1_clksel()), CTimerInstance::CTimer2 => (mrcc0.mrcc_ctimer2_clkdiv(), mrcc0.mrcc_ctimer2_clksel()), CTimerInstance::CTimer3 => (mrcc0.mrcc_ctimer3_clkdiv(), mrcc0.mrcc_ctimer3_clksel()), CTimerInstance::CTimer4 => (mrcc0.mrcc_ctimer4_clkdiv(), mrcc0.mrcc_ctimer4_clksel()), }; let (freq, variant) = match self.source { CTimerClockSel::FroLfDiv => { let freq = clocks.ensure_fro_lf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = CtimerClkselMux::CLKROOT_FUNC_0; #[cfg(feature = "mcxa5xx")] let mux = CtimerClkselMux::I0_CLKROOT_SIRC_DIV; (freq, mux) } CTimerClockSel::FroHfDiv => { let freq = clocks.ensure_fro_hf_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = CtimerClkselMux::CLKROOT_FUNC_1; #[cfg(feature = "mcxa5xx")] let mux = CtimerClkselMux::I1_CLKROOT_FIRC_GATED; (freq, mux) } #[cfg(not(feature = "sosc-as-gpio"))] CTimerClockSel::ClkIn => { let freq = clocks.ensure_clk_in_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = CtimerClkselMux::CLKROOT_FUNC_3; #[cfg(feature = "mcxa5xx")] let mux = CtimerClkselMux::I3_CLKROOT_SOSC; (freq, mux) } #[cfg(feature = "mcxa2xx")] CTimerClockSel::Clk16K => { let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = CtimerClkselMux::CLKROOT_FUNC_4; // TODO: MCXA5xx uses "LPOSC", which can either be clk_16k or clk_32k. // We do not support this yet. // #[cfg(feature = "mcxa5xx")] // let mux = CtimerClkselMux::I4_CLKROOT_LPOSC; (freq, mux) } CTimerClockSel::Clk1M => { let freq = clocks.ensure_clk_1m_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = CtimerClkselMux::CLKROOT_FUNC_5; #[cfg(feature = "mcxa5xx")] let mux = CtimerClkselMux::I5_CLKROOT_1M; (freq, mux) } CTimerClockSel::Pll1ClkDiv => { let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; // TODO: fix PAC names for consistency #[cfg(feature = "mcxa2xx")] let mux = CtimerClkselMux::CLKROOT_FUNC_6; #[cfg(feature = "mcxa5xx")] let mux = CtimerClkselMux::I6_CLKROOT_SPLL_DIV; (freq, mux) } CTimerClockSel::None => { // no ClkrootFunc7, just write manually for now clksel.write(|w| w.set_mux(CtimerClkselMux::_RESERVED_7)); clkdiv.modify(|w| { w.set_reset(ClkdivReset::ON); w.set_halt(ClkdivHalt::ON) }); return Ok(PreEnableParts::empty()); } }; let div = self.div.into_divisor(); let expected = freq / div; // 22.3.2 peripheral clock max functional clock limits let power = match self.power { PoweredClock::NormalEnabledDeepSleepDisabled => clocks.active_power, PoweredClock::AlwaysEnabled => clocks.lp_power, }; #[cfg(feature = "mcxa2xx")] let fmax = match power { VddLevel::MidDriveMode => 25_000_000, VddLevel::OverDriveMode => 60_000_000, }; #[cfg(feature = "mcxa5xx")] let fmax = match power { VddLevel::MidDriveMode => 50_000_000, VddLevel::NormalMode => 150_000_000, VddLevel::OverDriveMode => 200_000_000, }; if expected > fmax { return Err(ClockError::BadConfig { clock: "ctimer fclk", reason: "exceeds max rating", }); } apply_div4!(self, clksel, clkdiv, variant, freq) } } ================================================ FILE: embassy-mcxa/src/clocks/sleep.rs ================================================ //! Deep sleep entry, exit, and clock recovery. use core::cell::Ref; use core::ops::Deref; use critical_section::CriticalSection; use super::CLOCKS; use super::types::{Clocks, PoweredClock}; use crate::pac; use crate::pac::cmc::vals::CkctrlCkmode; #[cfg(feature = "mcxa2xx")] use crate::pac::scg::vals::Fircvld; use crate::pac::scg::vals::{Sircvld, SpllLock}; /// Attempt to go to deep sleep if possible. /// /// If we successfully went and returned from deep sleep, this function returns a `true`. /// If we were unsuccessful due to active `WaitGuard`s, this function returns a `false`. /// /// ## SAFETY /// /// Care must be taken that we have ensured that the system is ready to go to deep /// sleep, otherwise HAL peripherals may misbehave. `crate::clocks::init()` must /// have been called and returned successfully, with a `CoreSleep` configuration /// set to DeepSleep (or lower). pub unsafe fn deep_sleep_if_possible(cs: &CriticalSection) -> bool { let inhibit = crate::clocks::active_wake_guards(cs); if inhibit { return false; } unsafe { // Yep, it's time to go to deep sleep. WHILE STILL IN the CS, get ready setup_deep_sleep(); // Here we go! // // It is okay to WFE with interrupts disabled: we have enabled SEVONPEND cortex_m::asm::dsb(); cortex_m::asm::wfe(); // Wakey wakey, eggs and bakey recover_deep_sleep(cs); } true } /// Prepare the system for deep sleep /// /// ## SAFETY /// /// Care must be taken that we have ensured that the system is ready to go to deep /// sleep, otherwise HAL peripherals may misbehave. `crate::clocks::init()` must /// have been called and returned successfully, with a `CoreSleep` configuration /// set to DeepSleep (or lower). unsafe fn setup_deep_sleep() { let cmc = nxp_pac::CMC; let spc = nxp_pac::SPC0; // Isolate/unpower external voltage domains spc.evd_cfg().write(|w| w.0 = 0); // To configure for Deep Sleep Low-Power mode entry: // // Write Fh to Clock Control (CKCTRL) cmc.ckctrl().modify(|w| w.set_ckmode(CkctrlCkmode::CKMODE1111)); // Write 1h to Power Mode Protection (PMPROT) cmc.pmprot().write(|w| w.0 = 1); // Write 1h to Global Power Mode Control (GPMCTRL) cmc.gpmctrl().modify(|w| w.set_lpmode(0b0001)); // Redundant? // cmc.pmctrlmain().modify(|w| w.set_lpmode(PmctrlmainLpmode::LPMODE0001)); // From the C SDK: // // Before executing WFI instruction read back the last register to // ensure all registers writes have completed. let _ = cmc.gpmctrl().read(); } /// Start back up after deep sleep returns /// /// ## SAFETY /// /// Care must be taken that we have ensured that the system is ready to go to deep /// sleep, otherwise HAL peripherals may misbehave. `crate::clocks::init()` must /// have been called and returned successfully, with a `CoreSleep` configuration /// set to DeepSleep (or lower). unsafe fn recover_deep_sleep(cs: &CriticalSection) { let cmc = nxp_pac::CMC; // Restart any necessary clocks unsafe { restart_active_only_clocks(cs); } // Re-raise the sleep level to WFE sleep in the off chance that the // user decides to call `wfe` on their own accord, and to avoid having // to re-set if we chill in WFE sleep mostly cmc.ckctrl().modify(|w| w.set_ckmode(CkctrlCkmode::CKMODE0001)); } /// Perform any actions necessary to re-initialize clocks after returning to active /// mode after a low power (e.g. deep sleep, power-off) state. /// /// ## Safety /// /// This should only be called in a critical section, immediately after waking up. unsafe fn restart_active_only_clocks(_cs: &CriticalSection) { let bref: Ref<'_, Option> = CLOCKS.borrow_ref(*_cs); let dref: &Option = bref.deref(); let Some(clocks) = dref else { return; }; let scg = pac::SCG0; // TODO: Restart clock monitors if necessary? Needs to be re-enabled // AFTER FRO12M has been started, and probably after clocks are // valid again. // // TODO: Timeout? Check error fields (at least for SPLL)? Clear // or reset any status bits? // Ensure FRO12M is up and running if let Some(fro12m) = clocks.fro_12m_root.as_ref() && !matches!(fro12m.power, PoweredClock::AlwaysEnabled) { while scg.sirccsr().read().sircvld() != Sircvld::ENABLED_AND_VALID {} } // Ensure FRO45M is up and running #[cfg(feature = "mcxa2xx")] if let Some(frohf) = clocks.fro_hf_root.as_ref() && !matches!(frohf.power, PoweredClock::AlwaysEnabled) { while scg.firccsr().read().fircvld() != Fircvld::ENABLED_AND_VALID {} } // Ensure SOSC is up and running #[cfg(not(feature = "sosc-as-gpio"))] if let Some(clk_in) = clocks.clk_in.as_ref() && !matches!(clk_in.power, PoweredClock::AlwaysEnabled) { while !scg.sosccsr().read().soscvld() {} } // Ensure SPLL is up and running if let Some(spll) = clocks.pll1_clk.as_ref() && !matches!(spll.power, PoweredClock::AlwaysEnabled) { while scg.spllcsr().read().spll_lock() != SpllLock::ENABLED_AND_VALID {} } } ================================================ FILE: embassy-mcxa/src/clocks/types.rs ================================================ //! Clock system types use core::sync::atomic::Ordering; use super::LIVE_HP_TOKENS; use crate::clocks::config::{CoreSleep, VddLevel}; /// A guard that will inhibit the device from entering deep sleep while /// it exists. #[must_use = "Wake Guard must be kept in order to prevent deep sleep"] pub struct WakeGuard { _x: (), } impl WakeGuard { /// Create a new wake guard, that increments the "live high power token" counts. /// /// This is typically used by HAL drivers (when a peripheral is clocked from an /// active-mode-only source) to inhibit sleep, OR by application code to prevent /// deep sleep as well. pub fn new() -> Self { _ = LIVE_HP_TOKENS.fetch_add(1, Ordering::AcqRel); Self { _x: () } } /// Helper method to potentially create a guard if necessary for a clock. pub fn for_power(level: &PoweredClock) -> Option { match level { PoweredClock::NormalEnabledDeepSleepDisabled => Some(Self::new()), PoweredClock::AlwaysEnabled => None, } } } impl Clone for WakeGuard { fn clone(&self) -> Self { // NOTE: Call load-bearing-new to clone, DO NOT just use the derive to // copy the ZST! Self::new() } } impl Default for WakeGuard { fn default() -> Self { Self::new() } } impl Drop for WakeGuard { fn drop(&mut self) { let old = LIVE_HP_TOKENS.fetch_sub(1, Ordering::AcqRel); // Ensure we didn't just underflow. assert!(old != 0); } } /// The `Clocks` structure contains the initialized state of the core system clocks /// /// These values are configured by providing /// [ClocksConfig](crate::clocks::config::ClocksConfig) to the /// [`init()`](super::init) function at boot time. #[derive(Default, Debug, Clone)] #[non_exhaustive] pub struct Clocks { /// Active power config pub active_power: VddLevel, /// Low-power power config pub lp_power: VddLevel, /// Is the bandgap enabled in active mode? pub bandgap_active: bool, /// Is the bandgap enabled in deep sleep mode? pub bandgap_lowpower: bool, /// Lowest sleep level pub core_sleep: CoreSleep, /// The `clk_in` is a clock provided by an external oscillator /// AKA SOSC #[cfg(not(feature = "sosc-as-gpio"))] pub clk_in: Option, // FRO180M/FRO192M stuff // /// `fro_hf_root` is the direct output of the `FRO180M`/`FRO192M` internal oscillator /// /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`/`clk_48m`, /// and `fro_hf_div`. pub fro_hf_root: Option, /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. pub fro_hf: Option, /// `clk_45m` (2xx) or `clk_48` (5xx) is a 45MHz/48MHz clock, sourced from `fro_hf`. pub clk_hf_fundamental: Option, /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. pub fro_hf_div: Option, // // End FRO180M/FRO192M // FRO12M stuff // /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator /// /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, /// `and `fro_lf_div`. pub fro_12m_root: Option, /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. pub fro_12m: Option, /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` pub clk_1m: Option, /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` pub fro_lf_div: Option, // // End FRO12M stuff /// `clk_16k_vsys` is one of two/three outputs of the `FRO16K` internal oscillator. /// /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in /// the system domain, such as the CMP and RTC. pub clk_16k_vsys: Option, /// `clk_16k_vdd_core` is one of two/three outputs of the `FRO16K` internal oscillator. /// /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in /// the VDD Core domain, such as the OSTimer or LPUarts. pub clk_16k_vdd_core: Option, /// `clk_16k_vbat` is one of three outputs of the `FRO16K` internal oscillator. /// /// Also referred to as `clk_16k[2]` in the datasheet. #[cfg(feature = "mcxa5xx")] pub clk_16k_vbat: Option, /// `clk_32k_vsys` is one of two/three outputs of the `FRO16K` internal oscillator. /// /// Also referred to as `clk_32k[0]` in the datasheet, it feeds peripherals in /// the system domain, such as the CMP and RTC. #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] pub clk_32k_vsys: Option, /// `clk_32k_vdd_core` is one of two/three outputs of the `FRO16K` internal oscillator. /// /// Also referred to as `clk_32k[1]` in the datasheet, it feeds peripherals in /// the VDD Core domain, such as the OSTimer or LPUarts. #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] pub clk_32k_vdd_core: Option, /// `clk_32k_vbat` is one of three outputs of the `FRO16K` internal oscillator. /// /// Also referred to as `clk_32k[2]` in the datasheet. #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] pub clk_32k_vbat: Option, /// `main_clk` is the main clock, upstream of the cpu/system clock. pub main_clk: Option, /// `CPU_CLK` or `SYSTEM_CLK` is the output of `main_clk`, run through the `AHBCLKDIV`, /// used for the CPU, AHB, APB, IPS bus, and some high speed peripherals. pub cpu_system_clk: Option, /// `pll1_clk` is the output of the main system PLL, `pll1`. pub pll1_clk: Option, /// `pll1_clk_div` is a configurable frequency clock, sourced from `pll1_clk` pub pll1_clk_div: Option, } /// `ClockError` is the main error returned when configuring or checking clock state #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum ClockError { /// The system clocks were never initialized by calling [`init()`](super::init) NeverInitialized, /// The [`init()`](super::init) function was called more than once AlreadyInitialized, /// The requested configuration was not possible to fulfill, as the system clocks /// were not configured in a compatible way BadConfig { clock: &'static str, reason: &'static str }, /// The requested configuration was not possible to fulfill, as the required system /// clocks have not yet been implemented. NotImplemented { clock: &'static str }, /// The requested peripheral could not be configured, as the steps necessary to /// enable it have not yet been implemented. UnimplementedConfig, } /// Information regarding a system clock #[derive(Debug, Clone)] pub struct Clock { /// The frequency, in Hz, of the given clock pub frequency: u32, /// The power state of the clock, e.g. whether it is active in deep sleep mode /// or not. pub power: PoweredClock, } /// The power state of a given clock. /// /// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep /// mode will be stopped. This means that any downstream usage, e.g. by peripherals, /// will also stop. /// /// In the future, we will provide an API for entering Deep Sleep, and if there are /// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into /// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PoweredClock { /// The given clock will NOT continue running in Deep Sleep mode NormalEnabledDeepSleepDisabled, /// The given clock WILL continue running in Deep Sleep mode AlwaysEnabled, } /// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". /// /// These methods are intended to be used by HAL peripheral implementors to ensure that their /// selected clocks are active at a suitable level at time of construction. These methods /// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. impl Clocks { fn ensure_clock_active( &self, clock: &Option, name: &'static str, at_level: &PoweredClock, ) -> Result { let Some(clk) = clock.as_ref() else { return Err(ClockError::BadConfig { clock: name, reason: "required but not active", }); }; if !clk.power.meets_requirement_of(at_level) { return Err(ClockError::BadConfig { clock: name, reason: "not low power active", }); } Ok(clk.frequency) } /// Ensure the `fro_lf_div` clock is active and valid at the given power state. #[inline] pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.fro_lf_div, "fro_lf_div", at_level) } /// Ensure the `fro_hf` clock is active and valid at the given power state. #[inline] pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.fro_hf, "fro_hf", at_level) } /// Ensure the `fro_hf_div` clock is active and valid at the given power state. #[inline] pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.fro_hf_div, "fro_hf_div", at_level) } /// Ensure the `clk_in` clock is active and valid at the given power state. #[cfg(not(feature = "sosc-as-gpio"))] #[inline] pub fn ensure_clk_in_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.clk_in, "clk_in", at_level) } /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { // NOTE: clk_16k is always active in low power mode Ok(self .clk_16k_vsys .as_ref() .ok_or(ClockError::BadConfig { clock: "clk_16k_vsys", reason: "required but not active", })? .frequency) } /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { // NOTE: clk_16k is always active in low power mode Ok(self .clk_16k_vdd_core .as_ref() .ok_or(ClockError::BadConfig { clock: "clk_16k_vdd_core", reason: "required but not active", })? .frequency) } /// Ensure the `clk_16k_vbat` clock is active and valid at the given power state. #[cfg(feature = "mcxa5xx")] pub fn ensure_clk_16k_vbat_active(&self, _at_level: &PoweredClock) -> Result { // NOTE: clk_16k is always active in low power mode Ok(self .clk_16k_vbat .as_ref() .ok_or(ClockError::BadConfig { clock: "clk_16k_vbat", reason: "required but not active", })? .frequency) } /// Ensure the `clk_32k_vsys` clock is active and valid at the given power state. #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] #[inline] pub fn ensure_clk_32k_vsys_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.clk_32k_vsys, "clk_32k_vsys", at_level) } /// Ensure the `clk_32k_vdd_core` clock is active and valid at the given power state. #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] #[inline] pub fn ensure_clk_32k_vdd_core_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.clk_32k_vdd_core, "clk_32k_vdd_core", at_level) } /// Ensure the `clk_32k_vbat` clock is active and valid at the given power state. #[cfg(all(feature = "mcxa5xx", not(feature = "rosc-32k-as-gpio")))] #[inline] pub fn ensure_clk_32k_vbat_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.clk_32k_vbat, "clk_32k_vbat", at_level) } /// Ensure the `clk_1m` clock is active and valid at the given power state. #[inline] pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.clk_1m, "clk_1m", at_level) } /// Ensure the `pll1_clk` clock is active and valid at the given power state. #[inline] pub fn ensure_pll1_clk_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.pll1_clk, "pll1_clk", at_level) } /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. #[inline] pub fn ensure_pll1_clk_div_active(&self, at_level: &PoweredClock) -> Result { self.ensure_clock_active(&self.pll1_clk_div, "pll1_clk_div", at_level) } /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result { let Some(clk) = self.cpu_system_clk.as_ref() else { return Err(ClockError::BadConfig { clock: "cpu_system_clk", reason: "required but not active", }); }; // Can the main_clk ever be active in deep sleep? I think it is gated? match at_level { PoweredClock::NormalEnabledDeepSleepDisabled => {} PoweredClock::AlwaysEnabled => { return Err(ClockError::BadConfig { clock: "main_clk", reason: "not low power active", }); } } Ok(clk.frequency) } pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result { let freq = self.ensure_cpu_system_clk_active(at_level)?; Ok(freq / 6) } } impl PoweredClock { /// Does THIS clock meet the power requirements of the OTHER clock? pub fn meets_requirement_of(&self, other: &Self) -> bool { match (self, other) { (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, } } } ================================================ FILE: embassy-mcxa/src/config.rs ================================================ // HAL configuration (minimal), mirroring embassy-imxrt style use crate::clocks::config::ClocksConfig; use crate::interrupt::Priority; #[non_exhaustive] pub struct Config { pub time_interrupt_priority: Priority, pub rtc_interrupt_priority: Priority, pub adc_interrupt_priority: Priority, pub gpio_interrupt_priority: Priority, pub wwdt_interrupt_priority: Priority, pub cdog_interrupt_priority: Priority, pub clock_cfg: ClocksConfig, } impl Default for Config { fn default() -> Self { Self { time_interrupt_priority: Priority::from(0), rtc_interrupt_priority: Priority::from(0), adc_interrupt_priority: Priority::from(0), gpio_interrupt_priority: Priority::from(0), wwdt_interrupt_priority: Priority::from(0), cdog_interrupt_priority: Priority::from(0), clock_cfg: ClocksConfig::default(), } } } ================================================ FILE: embassy-mcxa/src/crc.rs ================================================ //! Cyclic Redundancy Check (CRC) use core::marker::PhantomData; use embassy_hal_internal::{Peri, PeripheralType}; use paste::paste; use crate::clocks::periph_helpers::NoConfig; use crate::clocks::{Gate, enable_and_reset}; use crate::pac; use crate::pac::crc::vals::{Fxor, Tcrc, Tot, Totr, Was}; /// CRC driver. pub struct Crc<'d, M> { info: &'static Info, _phantom: PhantomData<&'d mut M>, } impl<'d, M: Mode> Crc<'d, M> { fn new_inner(_peri: Peri<'d, T>) -> Self { // NoConfig? No WakeGuard! _ = unsafe { enable_and_reset::(&NoConfig) }; Crc { info: T::info(), _phantom: PhantomData, } } // Configure the underlying peripheral according to the reference manual. fn configure(&mut self, config: Config, width: Tcrc) { self.info.regs().ctrl().modify(|w| { w.set_fxor(config.complement_out.into()); w.set_totr(config.reflect_out.into()); w.set_tot(config.reflect_in.into()); w.set_was(Was::DATA); w.set_tcrc(width); }); self.info.regs().gpoly32().write(|w| *w = config.polynomial); self.info.regs().ctrl().modify(|w| w.set_was(Was::SEED)); self.info.regs().data32().write(|w| *w = config.seed); self.info.regs().ctrl().modify(|w| w.set_was(Was::DATA)); } /// Read the computed CRC value fn finalize_inner(self) -> W { // Reference manual states: // // "After writing all the data, you must wait for at least two // clock cycles to read the data from CRC Data (DATA) // register." cortex_m::asm::delay(2); W::read(self.info.regs()) } fn feed_word(&mut self, word: W) { W::write(self.info.regs(), word); } /// Feeds a slice of `Word`s into the CRC peripheral. Returns the computed /// checksum. /// /// The input is strided efficiently into as many `u32`s as possible, /// falling back to smaller writes for the remainder. fn feed_inner(&mut self, data: &[W]) { let (prefix, aligned, suffix) = unsafe { data.align_to::() }; for w in prefix { self.feed_word(*w); } for w in aligned { self.feed_word(*w); } for w in suffix { self.feed_word(*w); } } } impl<'d> Crc<'d, Crc16> { /// Instantiates a new CRC peripheral driver in 16-bit mode pub fn new_crc16(peri: Peri<'d, T>, config: Config) -> Self { let mut inst = Self::new_inner(peri); inst.configure(config, Tcrc::B16); inst } /// Instantiates a new CRC peripheral driver for the given `Algorithm16`. pub fn new_algorithm16(peri: Peri<'d, T>, algorithm: Algorithm16) -> Self { Self::new_crc16(peri, algorithm.into_config()) } /// Instantiates a new CRC peripheral for the `A` algorithm. pub fn new_a(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::A) } /// Instantiates a new CRC peripheral for the `AugCcitt` algorithm. pub fn new_aug_ccitt(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::AugCcitt) } /// Instantiates a new CRC peripheral for the `Arc` algorithm. pub fn new_arc(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Arc) } /// Instantiates a new CRC peripheral for the `Buypass` algorithm. pub fn new_buypass(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Buypass) } /// Instantiates a new CRC peripheral for the `CcittFalse` algorithm. pub fn new_ccitt_false(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::CcittFalse) } /// Instantiates a new CRC peripheral for the `CcittZero` algorithm. pub fn new_ccitt_zero(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::CcittZero) } /// Instantiates a new CRC peripheral for the `Cdma2000` algorithm. pub fn new_cdma_2000(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Cdma2000) } /// Instantiates a new CRC peripheral for the `Dds110` algorithm. pub fn new_dds_110(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Dds110) } /// Instantiates a new CRC peripheral for the `DectX` algorithm. pub fn new_dect_x(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::DectX) } /// Instantiates a new CRC peripheral for the `Dnp` algorithm. pub fn new_dnp(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Dnp) } /// Instantiates a new CRC peripheral for the `En13757` algorithm. pub fn new_en13757(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::En13757) } /// Instantiates a new CRC peripheral for the `Genibus` algorithm. pub fn new_genibus(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Genibus) } /// Instantiates a new CRC peripheral for the `Kermit` algorithm. pub fn new_kermit(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Kermit) } /// Instantiates a new CRC peripheral for the `Maxim` algorithm. pub fn new_maxim(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Maxim) } /// Instantiates a new CRC peripheral for the `Mcrf4xx` algorithm. pub fn new_mcrf4xx(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Mcrf4xx) } /// Instantiates a new CRC peripheral for the `Modbus` algorithm. pub fn new_modbus(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Modbus) } /// Instantiates a new CRC peripheral for the `Riello` algorithm. pub fn new_riello(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Riello) } /// Instantiates a new CRC peripheral for the `T10Dif` algorithm. pub fn new_t10_dif(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::T10Dif) } /// Instantiates a new CRC peripheral for the `Teledisk` algorithm. pub fn new_teledisk(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Teledisk) } /// Instantiates a new CRC peripheral for the `Tms37157` algorithm. pub fn new_tms_37157(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Tms37157) } /// Instantiates a new CRC peripheral for the `Usb` algorithm. pub fn new_usb(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Usb) } /// Instantiates a new CRC peripheral for the `X25` algorithm. pub fn new_x25(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::X25) } /// Instantiates a new CRC peripheral for the `Xmodem` algorithm. pub fn new_xmodem(peri: Peri<'d, T>) -> Self { Self::new_algorithm16(peri, Algorithm16::Xmodem) } /// Feeds a slice of `Word`s into the CRC peripheral. /// /// The input is strided efficiently into as many `u32`s as possible, /// falling back to smaller writes for the remainder. pub fn feed(&mut self, data: &[W]) { self.feed_inner(data); } /// Finalizes the CRC calculation and reads the resulting CRC from the /// hardware consuming `self`. pub fn finalize(self) -> u16 { self.finalize_inner() } } impl<'d> Crc<'d, Crc32> { /// Instantiates a new CRC peripheral driver in 32-bit mode pub fn new_crc32(peri: Peri<'d, T>, config: Config) -> Self { let mut inst = Self::new_inner(peri); inst.configure(config, Tcrc::B32); inst } /// Instantiates a new CRC peripheral driver for the given `Algorithm32`. pub fn new_algorithm32(peri: Peri<'d, T>, algorithm: Algorithm32) -> Self { Self::new_crc32(peri, algorithm.into_config()) } /// Instantiates a new CRC peripheral for the `Bzip2` algorithm. pub fn new_bzip2(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::Bzip2) } /// Instantiates a new CRC peripheral for the `C` algorithm. pub fn new_c(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::C) } /// Instantiates a new CRC peripheral for the `D` algorithm. pub fn new_d(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::D) } /// Instantiates a new CRC peripheral for the `IsoHdlc` algorithm. pub fn new_iso_hdlc(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::IsoHdlc) } /// Instantiates a new CRC peripheral for the `JamCrc` algorithm. pub fn new_jam_crc(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::JamCrc) } /// Instantiates a new CRC peripheral for the `Mpeg2` algorithm. pub fn new_mpeg2(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::Mpeg2) } /// Instantiates a new CRC peripheral for the `Posix` algorithm. pub fn new_posix(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::Posix) } /// Instantiates a new CRC peripheral for the `Q` algorithm. pub fn new_q(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::Q) } /// Instantiates a new CRC peripheral for the `Xfer` algorithm. pub fn new_xfer(peri: Peri<'d, T>) -> Self { Self::new_algorithm32(peri, Algorithm32::Xfer) } /// Feeds a slice of `Word`s into the CRC peripheral. /// /// The input is strided efficiently into as many `u32`s as possible, /// falling back to smaller writes for the remainder. pub fn feed(&mut self, data: &[W]) { self.feed_inner(data); } /// Finalizes the CRC calculation and reads the resulting CRC from the /// hardware consuming `self`. pub fn finalize(self) -> u32 { self.finalize_inner() } } mod sealed { pub trait SealedMode {} pub trait SealedWord: Copy { fn write(regs: crate::pac::crc::Crc, word: Self); fn read(regs: crate::pac::crc::Crc) -> Self; } } /// Mode of operation: 32 or 16-bit CRC. #[allow(private_bounds)] pub trait Mode: sealed::SealedMode {} /// 16-bit CRC. pub struct Crc16; impl sealed::SealedMode for Crc16 {} impl Mode for Crc16 {} /// 32-bit CRC. pub struct Crc32; impl sealed::SealedMode for Crc32 {} impl Mode for Crc32 {} /// Word size for the CRC. #[allow(private_bounds)] pub trait Word: sealed::SealedWord {} macro_rules! impl_word { ($t:ty, $width:literal, $write:expr, $read:expr) => { impl sealed::SealedWord for $t { #[inline] fn write(regs: crate::pac::crc::Crc, word: Self) { $write(regs, word) } #[inline] fn read(regs: crate::pac::crc::Crc) -> Self { $read(regs) } } impl Word for $t {} }; } impl_word!(u8, 8, write_u8, read_u8); impl_word!(u16, 16, write_u16, read_u16); impl_word!(u32, 32, write_u32, read_u32); fn write_u8(regs: crate::pac::crc::Crc, word: u8) { regs.data8().write(|w| *w = word); } fn read_u8(regs: crate::pac::crc::Crc) -> u8 { regs.data8().read() } fn write_u16(regs: crate::pac::crc::Crc, word: u16) { regs.data16().write(|w| *w = word); } fn read_u16(regs: crate::pac::crc::Crc) -> u16 { let ctrl = regs.ctrl().read(); // if transposition is enabled, result sits in the upper 16 bits if matches!(ctrl.totr(), Totr::BYTS_TRNPS | Totr::BYTS_BTS_TRNPS) { (regs.data32().read() >> 16) as u16 } else { regs.data16().read() } } fn write_u32(regs: crate::pac::crc::Crc, word: u32) { regs.data32().write(|w| *w = word); } fn read_u32(regs: crate::pac::crc::Crc) -> u32 { regs.data32().read() } /// CRC configuration. #[derive(Copy, Clone, Debug)] #[non_exhaustive] pub struct Config { /// The CRC polynomial to be used. pub polynomial: u32, /// Reflect bit order of input? pub reflect_in: Reflect, /// Reflect CRC bit order? pub reflect_out: Reflect, /// 1's complement CRC? pub complement_out: Complement, /// CRC Seed pub seed: u32, } impl Config { /// Create a new CRC config. #[must_use] pub fn new( polynomial: u32, reflect_in: Reflect, reflect_out: Reflect, complement_out: Complement, seed: u32, ) -> Self { Config { polynomial, reflect_in, reflect_out, complement_out, seed, } } } impl Default for Config { fn default() -> Self { Self { polynomial: 0, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0xffff_ffff, } } } /// Supported standard CRC16 algorithms. #[derive(Copy, Clone, Debug)] pub enum Algorithm16 { A, Arc, AugCcitt, Buypass, CcittFalse, CcittZero, Cdma2000, Dds110, DectX, Dnp, En13757, Genibus, Kermit, Maxim, Mcrf4xx, Modbus, Riello, T10Dif, Teledisk, Tms37157, Usb, X25, Xmodem, } impl Algorithm16 { fn into_config(self) -> Config { match self { Algorithm16::A => Config { polynomial: 0x1021, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0xc6c6, }, Algorithm16::Arc => Config { polynomial: 0x8005, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0, }, Algorithm16::AugCcitt => Config { polynomial: 0x1021, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0x1d0f, }, Algorithm16::Buypass => Config { polynomial: 0x8005, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, Algorithm16::CcittFalse => Config { polynomial: 0x1021, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0xffff, }, Algorithm16::CcittZero => Config { polynomial: 0x1021, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, Algorithm16::Cdma2000 => Config { polynomial: 0xc867, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0xffff, }, Algorithm16::Dds110 => Config { polynomial: 0x8005, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0x800d, }, Algorithm16::DectX => Config { polynomial: 0x0589, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, Algorithm16::Dnp => Config { polynomial: 0x3d65, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::Yes, seed: 0, }, Algorithm16::En13757 => Config { polynomial: 0x3d65, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::Yes, seed: 0, }, Algorithm16::Genibus => Config { polynomial: 0x1021, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::Yes, seed: 0xffff, }, Algorithm16::Kermit => Config { polynomial: 0x1021, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0, }, Algorithm16::Maxim => Config { polynomial: 0x8005, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::Yes, seed: 0, }, Algorithm16::Mcrf4xx => Config { polynomial: 0x1021, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0xffff, }, Algorithm16::Modbus => Config { polynomial: 0x8005, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0xffff, }, Algorithm16::Riello => Config { polynomial: 0x1021, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0xb2aa, }, Algorithm16::T10Dif => Config { polynomial: 0x8bb7, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, Algorithm16::Teledisk => Config { polynomial: 0xa097, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, Algorithm16::Tms37157 => Config { polynomial: 0x1021, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0x89ec, }, Algorithm16::Usb => Config { polynomial: 0x8005, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0xffff, }, Algorithm16::X25 => Config { polynomial: 0x1021, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::Yes, seed: 0xffff, }, Algorithm16::Xmodem => Config { polynomial: 0x1021, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, } } } /// Supported standard CRC32 algorithms. #[derive(Copy, Clone, Debug)] pub enum Algorithm32 { Bzip2, C, D, IsoHdlc, JamCrc, Mpeg2, Posix, Q, Xfer, } impl Algorithm32 { fn into_config(self) -> Config { match self { Algorithm32::Bzip2 => Config { polynomial: 0x04c1_1db7, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::Yes, seed: 0xffff_ffff, }, Algorithm32::C => Config { polynomial: 0x1edc_6f41, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::Yes, seed: 0xffff_ffff, }, Algorithm32::D => Config { polynomial: 0xa833_982b, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::Yes, seed: 0xffff_ffff, }, Algorithm32::IsoHdlc => Config { polynomial: 0x04c1_1db7, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::Yes, seed: 0xffff_ffff, }, Algorithm32::JamCrc => Config { polynomial: 0x04c1_1db7, reflect_in: Reflect::Yes, reflect_out: Reflect::Yes, complement_out: Complement::No, seed: 0xffff_ffff, }, Algorithm32::Mpeg2 => Config { polynomial: 0x04c1_1db7, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0xffff_ffff, }, Algorithm32::Posix => Config { polynomial: 0x04c1_1db7, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::Yes, seed: 0, }, Algorithm32::Q => Config { polynomial: 0x8141_41ab, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, Algorithm32::Xfer => Config { polynomial: 0x0000_00af, reflect_in: Reflect::No, reflect_out: Reflect::No, complement_out: Complement::No, seed: 0, }, } } } /// Reflect bit order. #[derive(Copy, Clone, Debug)] pub enum Reflect { No, Yes, } impl From for Tot { fn from(value: Reflect) -> Tot { match value { Reflect::No => Tot::BYTS_TRNPS, Reflect::Yes => Tot::BYTS_BTS_TRNPS, } } } impl From for Totr { fn from(value: Reflect) -> Totr { match value { Reflect::No => Totr::NOTRNPS, Reflect::Yes => Totr::BYTS_BTS_TRNPS, } } } /// 1's complement output. #[derive(Copy, Clone, Debug)] pub enum Complement { No, Yes, } impl From for Fxor { fn from(value: Complement) -> Fxor { match value { Complement::No => Fxor::NOXOR, Complement::Yes => Fxor::INVERT, } } } trait SealedInstance: Gate { fn info() -> &'static Info; } /// CRC Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send {} struct Info { regs: pac::crc::Crc, } impl Info { #[inline(always)] fn regs(&self) -> pac::crc::Crc { self.regs } } unsafe impl Sync for Info {} macro_rules! impl_instance { ($($n:literal),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], }; &INFO } } impl Instance for crate::peripherals::[] {} } )* }; } impl_instance!(0); ================================================ FILE: embassy-mcxa/src/ctimer/capture.rs ================================================ //! CTimer-based Capture driver use core::fmt; use core::marker::PhantomData; use core::ops::{Add, Sub}; use core::sync::atomic::Ordering; use embassy_hal_internal::Peri; use nxp_pac::ctimer::vals::{Capfe, Capi, Capre}; use super::{AnyChannel, CTimer, CTimerChannel, Channel, Info, InputPin, Instance}; use crate::clocks::WakeGuard; use crate::gpio::{AnyPin, SealedPin}; use crate::inputmux::{SealedValidInputMuxConfig, ValidInputMuxConfig}; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; /// Capture error. #[derive(Debug)] #[non_exhaustive] pub enum CaptureError { /// Other Other, } /// Capture configuration #[derive(Debug, Copy, Clone, Default)] #[non_exhaustive] pub struct Config { /// Edge capture pub edge: Edge, } /// Capture configuration #[derive(Debug, Copy, Clone, Default)] pub enum Edge { /// Rising edge RisingEdge, /// Falling edge FallingEdge, /// Both edges #[default] Both, } /// Timestamp capture /// /// Timestamp value in ticks. #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Timestamp(u32); impl Timestamp { #[inline] fn ticks(self) -> u32 { self.0 } #[inline] fn with_ticks(self, ticks: u32) -> Self { Self(ticks) } } #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TicksDiff(pub i32); impl TicksDiff { #[inline] pub fn to_period(self, tick_hz: u32) -> f32 { assert!(tick_hz != 0); self.0 as f32 / tick_hz as f32 } #[inline] pub fn to_frequency(self, tick_hz: u32) -> f32 { assert!(self.0 != 0); tick_hz as f32 / self.0 as f32 } #[inline] pub fn abs(self) -> TicksDiff { TicksDiff(self.0.abs()) } } impl fmt::Debug for TicksDiff { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} ticks", self.0) } } impl Add for Timestamp { type Output = TicksDiff; #[inline] fn add(self, rhs: Self) -> Self::Output { let lhs = self.ticks() as i32; let rhs = rhs.ticks() as i32; let raw = lhs.wrapping_add(rhs); TicksDiff(raw) } } impl Add for Timestamp { type Output = Timestamp; #[inline] fn add(self, rhs: u32) -> Self::Output { self.with_ticks(self.ticks().wrapping_add(rhs)) } } impl Add for Timestamp { type Output = Timestamp; #[inline] fn add(self, rhs: TicksDiff) -> Self::Output { let t = self.ticks().wrapping_add(rhs.0 as u32); self.with_ticks(t) } } impl Sub for Timestamp { type Output = TicksDiff; #[inline] fn sub(self, rhs: Self) -> Self::Output { let lhs = self.ticks() as i32; let rhs = rhs.ticks() as i32; let raw = lhs.wrapping_sub(rhs); TicksDiff(raw) } } impl Sub for Timestamp { type Output = Timestamp; #[inline] fn sub(self, rhs: u32) -> Self::Output { self.with_ticks(self.ticks().wrapping_sub(rhs)) } } impl Sub for Timestamp { type Output = Timestamp; #[inline] fn sub(self, rhs: TicksDiff) -> Self::Output { // Subtracting a signed diff == adding the negated diff let t = self.ticks().wrapping_sub(rhs.0 as u32); self.with_ticks(t) } } /// Capture driver pub struct Capture<'d> { info: &'static Info, ch: Peri<'d, AnyChannel>, pin: Peri<'d, AnyPin>, source_freq: u32, _wg: Option, } impl<'d> Capture<'d> { /// Create Capture driver /// /// Upon `Drop`, the external `pin` will be placed into `Disabled` /// state. pub fn new_with_input_pin, PIN: InputPin>( ctimer: CTimer<'d>, ch: Peri<'d, CH>, pin: Peri<'d, PIN>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result where (T, CH, PIN): ValidInputMuxConfig, { pin.mux(); <(T, CH, PIN) as SealedValidInputMuxConfig>::mux(); let mut inst = Self { info: T::info(), ch: ch.into(), pin: pin.into(), source_freq: ctimer._freq, _wg: ctimer._wg.clone(), }; inst.set_configuration(&config)?; T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable() }; // Enable CTimer inst.info.regs().tcr().modify(|w| w.set_cen(true)); Ok(inst) } fn set_configuration(&mut self, config: &Config) -> Result<(), CaptureError> { self.info.regs().ccr().modify(|w| { match self.ch.number() { Channel::Zero => match config.edge { Edge::Both => { w.set_cap0re(Capre::CAPRE1); w.set_cap0fe(Capfe::CAPFE1); } Edge::RisingEdge => { w.set_cap0re(Capre::CAPRE1); } Edge::FallingEdge => { w.set_cap0fe(Capfe::CAPFE1); } }, Channel::One => match config.edge { Edge::Both => { w.set_cap1re(Capre::CAPRE1); w.set_cap1fe(Capfe::CAPFE1); } Edge::RisingEdge => { w.set_cap1re(Capre::CAPRE1); } Edge::FallingEdge => { w.set_cap1fe(Capfe::CAPFE1); } }, Channel::Two => match config.edge { Edge::Both => { w.set_cap2re(Capre::CAPRE1); w.set_cap2fe(Capfe::CAPFE1); } Edge::RisingEdge => { w.set_cap2re(Capre::CAPRE1); } Edge::FallingEdge => { w.set_cap2fe(Capfe::CAPFE1); } }, Channel::Three => match config.edge { Edge::Both => { w.set_cap3re(Capre::CAPRE1); w.set_cap3fe(Capfe::CAPFE1); } Edge::RisingEdge => { w.set_cap3re(Capre::CAPRE1); } Edge::FallingEdge => { w.set_cap3fe(Capfe::CAPFE1); } }, }; }); Ok(()) } pub fn frequency(&self) -> u32 { self.source_freq } pub async fn capture(&mut self) -> Result { self.info .wait_cell() .wait_for(|| { self.info.regs().ccr().modify(|w| match self.ch.number() { Channel::Zero => { w.set_cap0i(Capi::CAPI1); } Channel::One => { w.set_cap1i(Capi::CAPI1); } Channel::Two => { w.set_cap2i(Capi::CAPI1); } Channel::Three => { w.set_cap3i(Capi::CAPI1); } }); let n: usize = self.ch.number().into(); let mask = 1 << n; (self.info.irq_flags().fetch_and(!mask, Ordering::AcqRel)) != 0 }) .await .map_err(|_| CaptureError::Other)?; let timestamp = self.info.regs().cr(self.ch.number().into()).read().cap(); Ok(Timestamp(timestamp)) } } impl<'d> Drop for Capture<'d> { fn drop(&mut self) { self.pin.set_as_disabled(); } } /// CTimer interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); // Clear interrupt status let ir = T::info().regs().ir().read(); T::info().regs().ir().write(|w| w.0 = ir.0); let mut mask = 0; T::info().regs().ccr().modify(|w| { if ir.cr0int() { w.set_cap0i(Capi::CAPI0); mask |= 1 << 0; } if ir.cr1int() { w.set_cap1i(Capi::CAPI0); mask |= 1 << 1; } if ir.cr2int() { w.set_cap2i(Capi::CAPI0); mask |= 1 << 2; } if ir.cr3int() { w.set_cap3i(Capi::CAPI0); mask |= 1 << 3; } }); T::info().irq_flags().fetch_or(mask, Ordering::Release); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } impl<'d> embassy_embedded_hal::SetConfig for Capture<'d> { type Config = Config; type ConfigError = CaptureError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_configuration(config) } } ================================================ FILE: embassy-mcxa/src/ctimer/mod.rs ================================================ //! CTimer driver. use core::marker::PhantomData; use core::sync::atomic::AtomicU8; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitCell; use paste::paste; use crate::clocks::periph_helpers::{CTimerClockSel, CTimerConfig, Div4}; use crate::clocks::{ClockError, Gate, PoweredClock, WakeGuard, enable_and_reset}; use crate::gpio::{GpioPin, SealedPin}; use crate::{interrupt, pac}; pub mod capture; pub mod pwm; /// CTimer channel #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Channel { /// Channel 0 Zero, /// Channel 1 One, /// Channel 2 Two, /// Channel 3 Three, } impl From for usize { fn from(value: Channel) -> usize { match value { Channel::Zero => 0, Channel::One => 1, Channel::Two => 2, Channel::Three => 3, } } } /// Error information type #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { /// Clock configuration error. ClockSetup(ClockError), /// Other internal errors or unexpected state. Other, } /// CTimer configuration #[derive(Debug, Copy, Clone)] #[non_exhaustive] pub struct Config { /// Powered clock configuration pub power: PoweredClock, /// CTimer clock source pub source: CTimerClockSel, /// CTimer pre-divider pub div: Div4, } impl Default for Config { fn default() -> Self { Self { power: PoweredClock::NormalEnabledDeepSleepDisabled, source: CTimerClockSel::FroLfDiv, div: const { Div4::no_div() }, } } } /// CTimer core driver. #[derive(Clone)] pub struct CTimer<'d> { _freq: u32, _wg: Option, _phantom: PhantomData<&'d mut ()>, } impl<'d> CTimer<'d> { /// Create a new instance of the CTimer core cdriver. pub fn new(_peri: Peri<'d, T>, config: Config) -> Result { // Enable clocks let conf = CTimerConfig { power: config.power, source: config.source, div: config.div, instance: T::CLOCK_INSTANCE, }; let parts = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; let inst = Self { _freq: parts.freq, _wg: parts.wake_guard, _phantom: PhantomData, }; Ok(inst) } } struct Info { regs: pac::ctimer::Ctimer, wait_cell: WaitCell, irq_flags: AtomicU8, } impl Info { #[inline(always)] fn regs(&self) -> pac::ctimer::Ctimer { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } #[inline(always)] fn irq_flags(&self) -> &AtomicU8 { &self.irq_flags } } unsafe impl Sync for Info {} trait SealedInstance: Gate { fn info() -> &'static Info; /// Clock instance const CLOCK_INSTANCE: crate::clocks::periph_helpers::CTimerInstance; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); } /// CTimer Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this I2C instance. type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_instance { ($peri:ident, $clock:ident, $perf:ident) => { impl SealedInstance for crate::peripherals::$peri { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::$peri, wait_cell: WaitCell::new(), irq_flags: const { AtomicU8::new(0) }, }; &INFO } const CLOCK_INSTANCE: crate::clocks::periph_helpers::CTimerInstance = crate::clocks::periph_helpers::CTimerInstance::$clock; paste! { const PERF_INT_INCR: fn() = crate::perf_counters::[]; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::[]; } } impl Instance for crate::peripherals::$peri { type Interrupt = crate::interrupt::typelevel::$peri; } }; } impl_instance!(CTIMER0, CTimer0, ctimer0); impl_instance!(CTIMER1, CTimer1, ctimer1); impl_instance!(CTIMER2, CTimer2, ctimer2); impl_instance!(CTIMER3, CTimer3, ctimer3); impl_instance!(CTIMER4, CTimer4, ctimer4); trait SealedCTimerChannel { fn number(&self) -> Channel; } /// CTimer channel #[allow(private_bounds)] pub trait CTimerChannel: SealedCTimerChannel + PeripheralType + Into + 'static + Send { } macro_rules! impl_channel { ($ch:ident, $peri:ident, $n:ident) => { impl SealedCTimerChannel for crate::peripherals::$ch { #[inline(always)] fn number(&self) -> Channel { Channel::$n } } impl CTimerChannel for crate::peripherals::$ch {} impl From for AnyChannel { fn from(value: crate::peripherals::$ch) -> Self { Self { number: value.number(), } } } }; } impl_channel!(CTIMER0_CH0, CTIMER0, Zero); impl_channel!(CTIMER0_CH1, CTIMER0, One); impl_channel!(CTIMER0_CH2, CTIMER0, Two); impl_channel!(CTIMER0_CH3, CTIMER0, Three); impl_channel!(CTIMER1_CH0, CTIMER1, Zero); impl_channel!(CTIMER1_CH1, CTIMER1, One); impl_channel!(CTIMER1_CH2, CTIMER1, Two); impl_channel!(CTIMER1_CH3, CTIMER1, Three); impl_channel!(CTIMER2_CH0, CTIMER2, Zero); impl_channel!(CTIMER2_CH1, CTIMER2, One); impl_channel!(CTIMER2_CH2, CTIMER2, Two); impl_channel!(CTIMER2_CH3, CTIMER2, Three); impl_channel!(CTIMER3_CH0, CTIMER3, Zero); impl_channel!(CTIMER3_CH1, CTIMER3, One); impl_channel!(CTIMER3_CH2, CTIMER3, Two); impl_channel!(CTIMER3_CH3, CTIMER3, Three); impl_channel!(CTIMER4_CH0, CTIMER4, Zero); impl_channel!(CTIMER4_CH1, CTIMER4, One); impl_channel!(CTIMER4_CH2, CTIMER4, Two); impl_channel!(CTIMER4_CH3, CTIMER4, Three); /// Type-erase CTIMER channel pub struct AnyChannel { number: Channel, } impl AnyChannel { fn number(&self) -> Channel { self.number } } embassy_hal_internal::impl_peripheral!(AnyChannel); /// Seal a trait trait SealedInputPin {} /// Seal a trait trait SealedOutputPin {} /// CTimer input pin. #[allow(private_bounds)] pub trait InputPin: GpioPin + SealedInputPin + PeripheralType { fn mux(&self); } /// CTimer output pin. #[allow(private_bounds)] pub trait OutputPin: GpioPin + SealedOutputPin + PeripheralType { fn mux(&self); } macro_rules! impl_input_pin { ($pin:ident, $fn:ident) => { impl SealedInputPin for crate::peripherals::$pin {} impl InputPin for crate::peripherals::$pin { #[inline(always)] fn mux(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); self.set_function(crate::pac::port::vals::Mux::$fn); self.set_enable_input_buffer(true); } } }; } macro_rules! impl_output_pin { ($pin:ident, $peri:ident, $fn:ident) => { impl SealedOutputPin for crate::peripherals::$pin {} impl OutputPin for crate::peripherals::$pin { #[inline(always)] fn mux(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Normal.into()); self.set_function(crate::pac::port::vals::Mux::$fn); self.set_enable_input_buffer(false); } } }; } #[cfg(feature = "mcxa2xx")] mod mcxa2xx { use super::*; // Input pins #[cfg(feature = "swd-as-gpio")] impl_input_pin!(P0_0, MUX4); #[cfg(feature = "swd-as-gpio")] impl_input_pin!(P0_1, MUX4); #[cfg(feature = "jtag-extras-as-gpio")] impl_input_pin!(P0_6, MUX4); impl_input_pin!(P0_20, MUX4); impl_input_pin!(P0_21, MUX4); impl_input_pin!(P0_22, MUX4); impl_input_pin!(P0_23, MUX4); impl_input_pin!(P1_0, MUX4); impl_input_pin!(P1_1, MUX4); impl_input_pin!(P1_2, MUX5); impl_input_pin!(P1_3, MUX5); impl_input_pin!(P1_6, MUX4); impl_input_pin!(P1_7, MUX4); impl_input_pin!(P1_8, MUX4); impl_input_pin!(P1_9, MUX4); impl_input_pin!(P1_14, MUX4); impl_input_pin!(P1_15, MUX4); #[cfg(feature = "sosc-as-gpio")] impl_input_pin!(P1_30, MUX4); #[cfg(feature = "sosc-as-gpio")] impl_input_pin!(P1_31, MUX4); impl_input_pin!(P2_0, MUX4); impl_input_pin!(P2_1, MUX4); impl_input_pin!(P2_2, MUX4); impl_input_pin!(P2_3, MUX4); impl_input_pin!(P2_4, MUX4); impl_input_pin!(P2_5, MUX4); impl_input_pin!(P2_6, MUX4); impl_input_pin!(P2_7, MUX4); impl_input_pin!(P3_0, MUX4); impl_input_pin!(P3_1, MUX4); impl_input_pin!(P3_8, MUX4); impl_input_pin!(P3_9, MUX4); impl_input_pin!(P3_14, MUX4); impl_input_pin!(P3_15, MUX4); impl_input_pin!(P3_16, MUX4); impl_input_pin!(P3_17, MUX4); impl_input_pin!(P3_22, MUX4); impl_input_pin!(P3_27, MUX4); impl_input_pin!(P3_28, MUX4); impl_input_pin!(P3_29, MUX4); impl_input_pin!(P4_6, MUX4); impl_input_pin!(P4_7, MUX4); // Output pins #[cfg(feature = "swd-swo-as-gpio")] impl_output_pin!(P0_2, CTIMER0, MUX4); #[cfg(feature = "jtag-extras-as-gpio")] impl_output_pin!(P0_3, CTIMER0, MUX4); impl_output_pin!(P0_16, CTIMER0, MUX4); impl_output_pin!(P0_17, CTIMER0, MUX4); impl_output_pin!(P0_18, CTIMER0, MUX4); impl_output_pin!(P0_19, CTIMER0, MUX4); impl_output_pin!(P0_22, CTIMER0, MUX5); impl_output_pin!(P0_23, CTIMER0, MUX5); impl_output_pin!(P1_0, CTIMER0, MUX5); impl_output_pin!(P1_1, CTIMER0, MUX5); impl_output_pin!(P1_2, CTIMER1, MUX4); impl_output_pin!(P1_3, CTIMER1, MUX4); impl_output_pin!(P1_4, CTIMER1, MUX4); impl_output_pin!(P1_5, CTIMER1, MUX4); impl_output_pin!(P1_6, CTIMER4, MUX5); impl_output_pin!(P1_7, CTIMER4, MUX5); impl_output_pin!(P1_8, CTIMER0, MUX5); impl_output_pin!(P1_9, CTIMER0, MUX5); impl_output_pin!(P1_10, CTIMER2, MUX4); impl_output_pin!(P1_11, CTIMER2, MUX4); impl_output_pin!(P1_12, CTIMER2, MUX4); impl_output_pin!(P1_13, CTIMER2, MUX4); impl_output_pin!(P1_14, CTIMER3, MUX5); impl_output_pin!(P1_15, CTIMER3, MUX5); impl_output_pin!(P2_0, CTIMER2, MUX5); impl_output_pin!(P2_1, CTIMER2, MUX5); impl_output_pin!(P2_2, CTIMER2, MUX5); impl_output_pin!(P2_3, CTIMER2, MUX5); impl_output_pin!(P2_4, CTIMER1, MUX5); impl_output_pin!(P2_5, CTIMER1, MUX5); impl_output_pin!(P2_6, CTIMER1, MUX5); impl_output_pin!(P2_7, CTIMER1, MUX5); impl_output_pin!(P2_10, CTIMER3, MUX4); impl_output_pin!(P2_11, CTIMER3, MUX4); impl_output_pin!(P2_12, CTIMER4, MUX4); impl_output_pin!(P2_12, CTIMER0, MUX5); impl_output_pin!(P2_13, CTIMER4, MUX4); impl_output_pin!(P2_13, CTIMER0, MUX5); impl_output_pin!(P2_15, CTIMER4, MUX5); impl_output_pin!(P2_15, CTIMER0, MUX5); impl_output_pin!(P2_16, CTIMER3, MUX5); impl_output_pin!(P2_16, CTIMER0, MUX5); impl_output_pin!(P2_17, CTIMER3, MUX5); impl_output_pin!(P2_17, CTIMER0, MUX5); impl_output_pin!(P2_19, CTIMER3, MUX4); impl_output_pin!(P2_20, CTIMER2, MUX4); impl_output_pin!(P2_21, CTIMER2, MUX4); impl_output_pin!(P2_23, CTIMER2, MUX4); impl_output_pin!(P3_2, CTIMER4, MUX4); impl_output_pin!(P3_6, CTIMER4, MUX4); impl_output_pin!(P3_7, CTIMER4, MUX4); impl_output_pin!(P3_10, CTIMER1, MUX4); impl_output_pin!(P3_11, CTIMER1, MUX4); impl_output_pin!(P3_12, CTIMER1, MUX4); impl_output_pin!(P3_13, CTIMER1, MUX4); impl_output_pin!(P3_18, CTIMER2, MUX4); impl_output_pin!(P3_19, CTIMER2, MUX4); impl_output_pin!(P3_20, CTIMER2, MUX4); impl_output_pin!(P3_21, CTIMER2, MUX4); impl_output_pin!(P3_27, CTIMER3, MUX5); impl_output_pin!(P3_28, CTIMER3, MUX5); #[cfg(feature = "dangerous-reset-as-gpio")] impl_output_pin!(P3_29, CTIMER3, MUX5); #[cfg(feature = "sosc-as-gpio")] impl_output_pin!(P3_30, CTIMER0, MUX4); #[cfg(feature = "sosc-as-gpio")] impl_output_pin!(P3_31, CTIMER0, MUX4); impl_output_pin!(P4_2, CTIMER4, MUX4); impl_output_pin!(P4_3, CTIMER4, MUX4); impl_output_pin!(P4_4, CTIMER4, MUX4); impl_output_pin!(P4_5, CTIMER4, MUX4); } #[cfg(feature = "mcxa5xx")] mod mcxa5xx { use super::*; // Input pins #[cfg(feature = "swd-as-gpio")] impl_input_pin!(P0_0, MUX4); #[cfg(feature = "swd-as-gpio")] impl_input_pin!(P0_1, MUX4); #[cfg(feature = "jtag-extras-as-gpio")] impl_input_pin!(P0_6, MUX4); impl_input_pin!(P0_7, MUX4); impl_input_pin!(P0_14, MUX4); impl_input_pin!(P0_15, MUX4); impl_input_pin!(P0_20, MUX4); impl_input_pin!(P0_21, MUX4); impl_input_pin!(P0_22, MUX4); impl_input_pin!(P0_23, MUX4); impl_input_pin!(P1_0, MUX4); impl_input_pin!(P1_1, MUX4); impl_input_pin!(P1_2, MUX5); impl_input_pin!(P1_3, MUX5); impl_input_pin!(P1_6, MUX4); impl_input_pin!(P1_7, MUX4); impl_input_pin!(P1_8, MUX4); impl_input_pin!(P1_9, MUX4); impl_input_pin!(P1_16, MUX4); impl_input_pin!(P1_17, MUX4); #[cfg(feature = "sosc-as-gpio")] impl_input_pin!(P1_30, MUX4); #[cfg(feature = "sosc-as-gpio")] impl_input_pin!(P1_31, MUX4); impl_input_pin!(P2_0, MUX4); impl_input_pin!(P2_1, MUX4); impl_input_pin!(P2_2, MUX4); impl_input_pin!(P2_3, MUX4); impl_input_pin!(P2_4, MUX4); impl_input_pin!(P2_5, MUX4); impl_input_pin!(P2_6, MUX4); impl_input_pin!(P2_7, MUX4); impl_input_pin!(P2_24, MUX4); impl_input_pin!(P2_25, MUX4); impl_input_pin!(P2_26, MUX4); impl_input_pin!(P3_0, MUX4); impl_input_pin!(P3_1, MUX4); impl_input_pin!(P3_4, MUX4); impl_input_pin!(P3_5, MUX4); impl_input_pin!(P3_8, MUX4); impl_input_pin!(P3_9, MUX4); impl_input_pin!(P3_14, MUX4); impl_input_pin!(P3_15, MUX4); impl_input_pin!(P3_16, MUX4); impl_input_pin!(P3_17, MUX4); impl_input_pin!(P3_22, MUX4); impl_input_pin!(P3_23, MUX4); impl_input_pin!(P3_24, MUX4); impl_input_pin!(P3_25, MUX4); impl_input_pin!(P3_26, MUX4); impl_input_pin!(P3_27, MUX4); impl_input_pin!(P3_28, MUX4); impl_input_pin!(P3_29, MUX4); impl_input_pin!(P4_6, MUX4); impl_input_pin!(P4_7, MUX4); // Output pins #[cfg(feature = "swd-swo-as-gpio")] impl_output_pin!(P0_2, CTIMER0, MUX4); #[cfg(feature = "jtag-extras-as-gpio")] impl_output_pin!(P0_3, CTIMER0, MUX4); impl_output_pin!(P0_4, CTIMER0, MUX4); impl_output_pin!(P0_5, CTIMER0, MUX4); impl_output_pin!(P0_12, CTIMER0, MUX4); impl_output_pin!(P0_13, CTIMER0, MUX4); impl_output_pin!(P0_16, CTIMER0, MUX4); impl_output_pin!(P0_17, CTIMER0, MUX4); impl_output_pin!(P0_18, CTIMER0, MUX4); impl_output_pin!(P0_19, CTIMER0, MUX4); impl_output_pin!(P0_22, CTIMER0, MUX5); impl_output_pin!(P0_23, CTIMER0, MUX5); impl_output_pin!(P0_24, CTIMER0, MUX4); impl_output_pin!(P0_25, CTIMER0, MUX4); impl_output_pin!(P0_26, CTIMER0, MUX4); impl_output_pin!(P0_27, CTIMER0, MUX4); impl_output_pin!(P1_0, CTIMER0, MUX5); impl_output_pin!(P1_1, CTIMER0, MUX5); impl_output_pin!(P1_2, CTIMER1, MUX4); impl_output_pin!(P1_3, CTIMER1, MUX4); impl_output_pin!(P1_4, CTIMER1, MUX4); impl_output_pin!(P1_5, CTIMER1, MUX4); impl_output_pin!(P1_6, CTIMER4, MUX5); impl_output_pin!(P1_7, CTIMER4, MUX5); impl_output_pin!(P1_8, CTIMER0, MUX5); impl_output_pin!(P1_9, CTIMER0, MUX5); impl_output_pin!(P1_10, CTIMER2, MUX4); impl_output_pin!(P1_11, CTIMER2, MUX4); impl_output_pin!(P1_12, CTIMER2, MUX4); impl_output_pin!(P1_13, CTIMER2, MUX4); impl_output_pin!(P1_14, CTIMER3, MUX5); impl_output_pin!(P1_15, CTIMER3, MUX5); impl_output_pin!(P1_18, CTIMER3, MUX4); impl_output_pin!(P1_19, CTIMER3, MUX4); impl_output_pin!(P2_0, CTIMER2, MUX5); impl_output_pin!(P2_1, CTIMER2, MUX5); impl_output_pin!(P2_2, CTIMER2, MUX5); impl_output_pin!(P2_3, CTIMER2, MUX5); impl_output_pin!(P2_4, CTIMER1, MUX5); impl_output_pin!(P2_5, CTIMER1, MUX5); impl_output_pin!(P2_6, CTIMER1, MUX5); impl_output_pin!(P2_7, CTIMER1, MUX5); impl_output_pin!(P2_8, CTIMER3, MUX4); impl_output_pin!(P2_9, CTIMER3, MUX4); impl_output_pin!(P2_10, CTIMER3, MUX4); impl_output_pin!(P2_11, CTIMER3, MUX4); impl_output_pin!(P2_12, CTIMER0, MUX5); impl_output_pin!(P2_12, CTIMER4, MUX4); impl_output_pin!(P2_13, CTIMER0, MUX5); impl_output_pin!(P2_13, CTIMER4, MUX4); impl_output_pin!(P2_14, CTIMER4, MUX4); impl_output_pin!(P2_15, CTIMER0, MUX5); impl_output_pin!(P2_15, CTIMER4, MUX4); impl_output_pin!(P2_16, CTIMER0, MUX5); impl_output_pin!(P2_16, CTIMER3, MUX4); impl_output_pin!(P2_17, CTIMER0, MUX5); impl_output_pin!(P2_17, CTIMER3, MUX4); impl_output_pin!(P2_18, CTIMER3, MUX4); impl_output_pin!(P2_19, CTIMER3, MUX4); impl_output_pin!(P2_20, CTIMER2, MUX4); impl_output_pin!(P2_21, CTIMER2, MUX4); impl_output_pin!(P2_22, CTIMER2, MUX4); impl_output_pin!(P2_23, CTIMER2, MUX4); impl_output_pin!(P3_2, CTIMER4, MUX4); impl_output_pin!(P3_3, CTIMER4, MUX4); impl_output_pin!(P3_6, CTIMER4, MUX4); impl_output_pin!(P3_7, CTIMER4, MUX4); impl_output_pin!(P3_10, CTIMER1, MUX4); impl_output_pin!(P3_11, CTIMER1, MUX4); impl_output_pin!(P3_12, CTIMER1, MUX4); impl_output_pin!(P3_13, CTIMER1, MUX4); impl_output_pin!(P3_18, CTIMER2, MUX4); impl_output_pin!(P3_19, CTIMER2, MUX4); impl_output_pin!(P3_20, CTIMER2, MUX4); impl_output_pin!(P3_21, CTIMER2, MUX4); impl_output_pin!(P3_27, CTIMER3, MUX5); impl_output_pin!(P3_28, CTIMER3, MUX5); #[cfg(feature = "dangerous-reset-as-gpio")] impl_output_pin!(P3_29, CTIMER3, MUX5); #[cfg(feature = "sosc-as-gpio")] impl_output_pin!(P3_30, CTIMER0, MUX4); #[cfg(feature = "sosc-as-gpio")] impl_output_pin!(P3_31, CTIMER0, MUX4); impl_output_pin!(P4_2, CTIMER4, MUX4); impl_output_pin!(P4_3, CTIMER4, MUX4); impl_output_pin!(P4_4, CTIMER4, MUX4); impl_output_pin!(P4_5, CTIMER4, MUX4); } ================================================ FILE: embassy-mcxa/src/ctimer/pwm.rs ================================================ //! CTimer-based PWM driver use embassy_hal_internal::Peri; pub use embedded_hal_1::pwm::SetDutyCycle; use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; use super::{AnyChannel, CTimer, CTimerChannel, Channel, Info, Instance, OutputPin}; use crate::gpio::{AnyPin, SealedPin}; use crate::pac::ctimer::vals::{Mri, Mrr, Mrrl, Mrs, Pwmen}; /// PWM error. #[derive(Debug)] pub enum PwmError { /// Invalid Duty Cycle. InvalidDutyCycle, /// Invalid Channel Number. InvalidChannel, /// Channel mismatch ChannelMismatch, } impl Error for PwmError { fn kind(&self) -> ErrorKind { match self { PwmError::InvalidDutyCycle => ErrorKind::Other, PwmError::InvalidChannel => ErrorKind::Other, PwmError::ChannelMismatch => ErrorKind::Other, } } } /// Pwm Configuration #[derive(Debug, Copy, Clone)] #[non_exhaustive] pub struct Config { /// The point at which the counter wraps around. /// /// This value represents the maximum possible period. pub freq: u16, /// Duty cycle. pub duty_cycle: u16, } impl Default for Config { fn default() -> Self { Self { freq: 20_000, duty_cycle: 0, } } } /// Representation of a single PWM channel. /// /// This PWM representation can change duty cycle, but not frequency /// of the PWM. pub struct Pwm<'d> { info: &'static Info, duty_ch: Peri<'d, AnyChannel>, pin: Peri<'d, AnyPin>, source_freq: u32, pwm_freq: u16, max_period: u16, } impl<'d> Pwm<'d> { fn enable(&mut self) { self.info.regs().tcr().modify(|w| w.set_cen(true)); } fn disable(&mut self) { self.info.regs().tcr().modify(|w| w.set_cen(false)); } fn set_pwm_mode(&self) { self.info.regs().pwmc().modify(|w| match self.duty_ch.number() { Channel::Zero => { w.set_pwmen0(Pwmen::PWM); } Channel::One => { w.set_pwmen1(Pwmen::PWM); } Channel::Two => { w.set_pwmen2(Pwmen::PWM); } Channel::Three => { w.set_pwmen3(Pwmen::PWM); } }); } fn clear_status(&self) { self.info.regs().mcr().modify(|w| { // Clear stop, reset, and interrupt bits for the PWM channel match self.duty_ch.number() { Channel::Zero => { w.set_mr0i(Mri::MRI0); w.set_mr0r(Mrr::MRR0); w.set_mr0s(Mrs::MRS0); } Channel::One => { w.set_mr1i(Mri::MRI0); w.set_mr1r(Mrr::MRR0); w.set_mr1s(Mrs::MRS0); } Channel::Two => { w.set_mr2i(Mri::MRI0); w.set_mr2r(Mrr::MRR0); w.set_mr2s(Mrs::MRS0); } Channel::Three => { w.set_mr3i(Mri::MRI0); w.set_mr3r(Mrr::MRR0); w.set_mr3s(Mrs::MRS0); } } match self.duty_ch.number() { Channel::Zero => { w.set_mr0rl(Mrrl::MRRL1); } Channel::One => { w.set_mr1rl(Mrrl::MRRL1); } Channel::Two => { w.set_mr2rl(Mrrl::MRRL1); } Channel::Three => { w.set_mr3rl(Mrrl::MRRL1); } } }); } fn configure_duty_cycle(&self, duty_cycle: u32) { self.info .regs() .mr(self.duty_ch.number().into()) .write(|w| w.set_match_(duty_cycle)); self.info .regs() .msr(self.duty_ch.number().into()) .write(|w| w.set_match_shadow(duty_cycle)); } } /// Single channel PWM driver /// /// A single channel is used for Duty Cycle and a single channel is /// used for PWM period match. pub struct SinglePwm<'d> { pwm: Pwm<'d>, period_ch: Peri<'d, AnyChannel>, } impl<'d> SinglePwm<'d> { /// Create Pwm driver with a single pin as output. /// /// Upon `Drop`, the external `pin` will be placed into `Disabled` /// state. pub fn new, PIN: OutputPin>( ctimer: CTimer<'d>, duty_ch: Peri<'d, DUTY>, period_ch: Peri<'d, impl CTimerChannel>, pin: Peri<'d, PIN>, config: Config, ) -> Result where (T, DUTY, PIN): ValidMatchConfig, { pin.mux(); let mut inst = Self { pwm: Pwm { info: T::info(), duty_ch: duty_ch.into(), source_freq: ctimer._freq, pwm_freq: config.freq, pin: pin.into(), max_period: 0, }, period_ch: period_ch.into(), }; inst.set_configuration(&config)?; Ok(inst) } /// Degrade `self` into the underlying PWM representation. /// /// Upon calling this method, changing frequency will be disallowed. pub fn degrade(self) -> Pwm<'d> { self.pwm } fn set_configuration(&mut self, config: &Config) -> Result<(), PwmError> { self.pwm.disable(); self.pwm.set_pwm_mode(); self.pwm.clear_status(); self.pwm.info.regs().mcr().modify(|w| match self.period_ch.number() { Channel::Zero => { w.set_mr0r(Mrr::MRR1); } Channel::One => { w.set_mr1r(Mrr::MRR1); } Channel::Two => { w.set_mr2r(Mrr::MRR1); } Channel::Three => { w.set_mr3r(Mrr::MRR1); } }); // Configure PWM period let period = self.pwm.source_freq / u32::from(self.pwm.pwm_freq) - 1; self.pwm.max_period = period as u16; self.pwm .info .regs() .mr(self.period_ch.number().into()) .write(|w| w.set_match_(period)); // Configure PWM duty cycle let duty_cycle = ((period + 1) * (100 - u32::from(config.duty_cycle))) / 100; self.pwm.configure_duty_cycle(duty_cycle); // Start CTimer self.pwm.enable(); Ok(()) } } /// Dual channel PWM driver. /// /// A single period match channel is shared for two independent PWM /// outputs. That is, both PWM output channels run on the same /// frequency, with optionally different duty cycles. pub struct DualPwm<'d> { pub pwm0: Pwm<'d>, pub pwm1: Pwm<'d>, period_ch: Peri<'d, AnyChannel>, } impl<'d> DualPwm<'d> { /// Create Pwm driver with a two pins for two PWM outputs. /// /// Upon `Drop`, all external pins will be placed into `Disabled` /// state. pub fn new, DUTY1: CTimerChannel, PIN0: OutputPin, PIN1: OutputPin>( ctimer: CTimer<'d>, duty_ch0: Peri<'d, DUTY0>, duty_ch1: Peri<'d, DUTY1>, period_ch: Peri<'d, impl CTimerChannel>, pin0: Peri<'d, PIN0>, pin1: Peri<'d, PIN1>, config: Config, ) -> Result where (T, DUTY0, PIN0): ValidMatchConfig, (T, DUTY1, PIN1): ValidMatchConfig, { pin0.mux(); pin1.mux(); let mut inst = Self { pwm0: Pwm { info: T::info(), duty_ch: duty_ch0.into(), source_freq: ctimer._freq, pwm_freq: config.freq, pin: pin0.into(), max_period: 0, }, pwm1: Pwm { info: T::info(), duty_ch: duty_ch1.into(), source_freq: ctimer._freq, pwm_freq: config.freq, pin: pin1.into(), max_period: 0, }, period_ch: period_ch.into(), }; inst.set_configuration(&config)?; Ok(inst) } /// Split `self` into its underlying channels. /// /// Upon calling this method, changing PWM frequency will be /// disallowed. Only duty cycles can be changed. pub fn split(self) -> (Pwm<'d>, Pwm<'d>) { (self.pwm0, self.pwm1) } fn set_configuration(&mut self, config: &Config) -> Result<(), PwmError> { self.pwm0.disable(); self.pwm0.set_pwm_mode(); self.pwm1.set_pwm_mode(); self.pwm0.clear_status(); self.pwm1.clear_status(); self.pwm0.info.regs().mcr().modify(|w| match self.period_ch.number() { Channel::Zero => { w.set_mr0r(Mrr::MRR1); } Channel::One => { w.set_mr1r(Mrr::MRR1); } Channel::Two => { w.set_mr2r(Mrr::MRR1); } Channel::Three => { w.set_mr3r(Mrr::MRR1); } }); // Configure PWM period let period = self.pwm0.source_freq / u32::from(self.pwm0.pwm_freq) - 1; self.pwm0.max_period = period as u16; self.pwm1.max_period = period as u16; self.pwm0 .info .regs() .mr(self.period_ch.number().into()) .write(|w| w.set_match_(period)); // Configure PWM duty cycle let duty_cycle = ((period + 1) * (100 - u32::from(config.duty_cycle))) / 100; self.pwm0.configure_duty_cycle(duty_cycle); self.pwm1.configure_duty_cycle(duty_cycle); // Start CTimer self.pwm0.enable(); Ok(()) } } /// Triple channel PWM driver. /// /// A single period match channel is shared for three independent PWM /// outputs. That is, all three PWM output channels run on the same /// frequency, with optionally different duty cycles. pub struct TriplePwm<'d> { pub pwm0: Pwm<'d>, pub pwm1: Pwm<'d>, pub pwm2: Pwm<'d>, period_ch: Peri<'d, AnyChannel>, } impl<'d> TriplePwm<'d> { /// Create Pwm driver using three pins for three PWM outputs. /// /// Upon `Drop`, all external pins will be placed into `Disabled` /// state. pub fn new< T: Instance, DUTY0: CTimerChannel, DUTY1: CTimerChannel, DUTY2: CTimerChannel, PIN0: OutputPin, PIN1: OutputPin, PIN2: OutputPin, >( ctimer: CTimer<'d>, duty_ch0: Peri<'d, DUTY0>, duty_ch1: Peri<'d, DUTY1>, duty_ch2: Peri<'d, DUTY2>, period_ch: Peri<'d, impl CTimerChannel>, pin0: Peri<'d, PIN0>, pin1: Peri<'d, PIN1>, pin2: Peri<'d, PIN2>, config: Config, ) -> Result where (T, DUTY0, PIN0): ValidMatchConfig, (T, DUTY1, PIN1): ValidMatchConfig, (T, DUTY2, PIN2): ValidMatchConfig, { pin0.mux(); pin1.mux(); pin2.mux(); let mut inst = Self { pwm0: Pwm { info: T::info(), duty_ch: duty_ch0.into(), source_freq: ctimer._freq, pwm_freq: config.freq, pin: pin0.into(), max_period: 0, }, pwm1: Pwm { info: T::info(), duty_ch: duty_ch1.into(), source_freq: ctimer._freq, pwm_freq: config.freq, pin: pin1.into(), max_period: 0, }, pwm2: Pwm { info: T::info(), duty_ch: duty_ch2.into(), source_freq: ctimer._freq, pwm_freq: config.freq, pin: pin2.into(), max_period: 0, }, period_ch: period_ch.into(), }; inst.set_configuration(&config)?; Ok(inst) } /// Split `self` into its underlying channels. /// /// Upon calling this method, changing PWM frequency will be /// disallowed. Only duty cycles can be changed. pub fn split(self) -> (Pwm<'d>, Pwm<'d>, Pwm<'d>) { (self.pwm0, self.pwm1, self.pwm2) } fn set_configuration(&mut self, config: &Config) -> Result<(), PwmError> { self.pwm0.disable(); self.pwm0.set_pwm_mode(); self.pwm1.set_pwm_mode(); self.pwm2.set_pwm_mode(); self.pwm0.clear_status(); self.pwm1.clear_status(); self.pwm2.clear_status(); self.pwm0.info.regs().mcr().modify(|w| match self.period_ch.number() { Channel::Zero => { w.set_mr0r(Mrr::MRR1); } Channel::One => { w.set_mr1r(Mrr::MRR1); } Channel::Two => { w.set_mr2r(Mrr::MRR1); } Channel::Three => { w.set_mr3r(Mrr::MRR1); } }); // Configure PWM period let period = self.pwm0.source_freq / u32::from(self.pwm0.pwm_freq) - 1; self.pwm0.max_period = period as u16; self.pwm1.max_period = period as u16; self.pwm2.max_period = period as u16; self.pwm0 .info .regs() .mr(self.period_ch.number().into()) .write(|w| w.set_match_(period)); // Configure PWM duty cycle let duty_cycle = ((period + 1) * (100 - u32::from(config.duty_cycle))) / 100; self.pwm0.configure_duty_cycle(duty_cycle); self.pwm1.configure_duty_cycle(duty_cycle); self.pwm2.configure_duty_cycle(duty_cycle); // Start CTimer self.pwm0.enable(); Ok(()) } } impl<'d> Drop for Pwm<'d> { fn drop(&mut self) { self.disable(); self.pin.set_as_disabled(); } } impl<'d> ErrorType for SinglePwm<'d> { type Error = PwmError; } impl<'d> SetDutyCycle for SinglePwm<'d> { fn max_duty_cycle(&self) -> u16 { self.pwm.max_period } fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { self.pwm.set_duty_cycle(duty) } } impl<'d> ErrorType for Pwm<'d> { type Error = PwmError; } impl<'d> SetDutyCycle for Pwm<'d> { fn max_duty_cycle(&self) -> u16 { self.max_period } 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); } self.info .regs() .msr(self.duty_ch.number().into()) .write(|w| w.set_match_shadow(u32::from(duty))); Ok(()) } } trait SealedValidMatchConfig {} /// Valid match channel + pin configuration marker trait #[allow(private_bounds)] pub trait ValidMatchConfig: SealedValidMatchConfig {} macro_rules! impl_valid_match { ($peri:ident, $ch:ident, $pin:ident, $n:literal) => { impl SealedValidMatchConfig for ( crate::peripherals::$peri, crate::peripherals::$ch, crate::peripherals::$pin, ) { } impl ValidMatchConfig for ( crate::peripherals::$peri, crate::peripherals::$ch, crate::peripherals::$pin, ) { } }; } #[cfg(feature = "mcxa2xx")] mod mcxa2xx { use super::*; // CTIMER0 match channels #[cfg(feature = "swd-swo-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_2, 0); #[cfg(feature = "jtag-extras-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_3, 1); impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_16, 0); impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_17, 1); impl_valid_match!(CTIMER0, CTIMER0_CH2, P0_18, 2); impl_valid_match!(CTIMER0, CTIMER0_CH3, P0_19, 3); impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_22, 0); impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_23, 1); impl_valid_match!(CTIMER0, CTIMER0_CH2, P1_0, 2); impl_valid_match!(CTIMER0, CTIMER0_CH3, P1_1, 3); #[cfg(feature = "sosc-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH2, P3_30, 2); #[cfg(feature = "sosc-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH3, P3_31, 3); // CTIMER1 match channels impl_valid_match!(CTIMER1, CTIMER1_CH0, P1_2, 0); impl_valid_match!(CTIMER1, CTIMER1_CH1, P1_3, 1); impl_valid_match!(CTIMER1, CTIMER1_CH2, P1_4, 2); impl_valid_match!(CTIMER1, CTIMER1_CH3, P1_5, 3); impl_valid_match!(CTIMER1, CTIMER1_CH0, P2_4, 0); impl_valid_match!(CTIMER1, CTIMER1_CH1, P2_5, 1); impl_valid_match!(CTIMER1, CTIMER1_CH2, P2_6, 2); impl_valid_match!(CTIMER1, CTIMER1_CH3, P2_7, 3); impl_valid_match!(CTIMER1, CTIMER1_CH0, P3_10, 0); impl_valid_match!(CTIMER1, CTIMER1_CH1, P3_11, 1); impl_valid_match!(CTIMER1, CTIMER1_CH2, P3_12, 2); impl_valid_match!(CTIMER1, CTIMER1_CH3, P3_13, 3); // CTIMER2 match channels impl_valid_match!(CTIMER2, CTIMER2_CH0, P1_10, 0); impl_valid_match!(CTIMER2, CTIMER2_CH1, P1_11, 1); impl_valid_match!(CTIMER2, CTIMER2_CH2, P1_12, 2); impl_valid_match!(CTIMER2, CTIMER2_CH3, P1_13, 3); impl_valid_match!(CTIMER2, CTIMER2_CH0, P2_0, 0); impl_valid_match!(CTIMER2, CTIMER2_CH1, P2_1, 1); impl_valid_match!(CTIMER2, CTIMER2_CH2, P2_2, 2); impl_valid_match!(CTIMER2, CTIMER2_CH3, P2_3, 3); impl_valid_match!(CTIMER2, CTIMER2_CH0, P2_20, 0); impl_valid_match!(CTIMER2, CTIMER2_CH1, P2_21, 1); impl_valid_match!(CTIMER2, CTIMER2_CH3, P2_23, 3); impl_valid_match!(CTIMER2, CTIMER2_CH0, P3_18, 0); impl_valid_match!(CTIMER2, CTIMER2_CH1, P3_19, 1); impl_valid_match!(CTIMER2, CTIMER2_CH2, P3_20, 2); impl_valid_match!(CTIMER2, CTIMER2_CH3, P3_21, 3); // CTIMER3 match channels impl_valid_match!(CTIMER3, CTIMER3_CH0, P1_14, 0); impl_valid_match!(CTIMER3, CTIMER3_CH1, P1_15, 1); impl_valid_match!(CTIMER3, CTIMER3_CH2, P2_10, 2); impl_valid_match!(CTIMER3, CTIMER3_CH3, P2_11, 3); impl_valid_match!(CTIMER3, CTIMER3_CH0, P2_16, 0); impl_valid_match!(CTIMER3, CTIMER3_CH1, P2_17, 1); impl_valid_match!(CTIMER3, CTIMER3_CH2, P2_19, 3); impl_valid_match!(CTIMER3, CTIMER3_CH0, P3_27, 1); impl_valid_match!(CTIMER3, CTIMER3_CH2, P3_28, 2); #[cfg(feature = "dangerous-reset-as-gpio")] impl_valid_match!(CTIMER3, CTIMER3_CH3, P3_29, 3); // CTIMER4 match channels impl_valid_match!(CTIMER4, CTIMER4_CH0, P1_6, 0); impl_valid_match!(CTIMER4, CTIMER4_CH1, P1_7, 1); impl_valid_match!(CTIMER4, CTIMER4_CH0, P2_12, 0); impl_valid_match!(CTIMER4, CTIMER4_CH1, P2_13, 1); impl_valid_match!(CTIMER4, CTIMER4_CH3, P2_15, 3); impl_valid_match!(CTIMER4, CTIMER4_CH0, P3_2, 0); impl_valid_match!(CTIMER4, CTIMER4_CH2, P3_6, 2); impl_valid_match!(CTIMER4, CTIMER4_CH3, P3_7, 3); impl_valid_match!(CTIMER4, CTIMER4_CH0, P4_2, 0); impl_valid_match!(CTIMER4, CTIMER4_CH1, P4_3, 1); impl_valid_match!(CTIMER4, CTIMER4_CH2, P4_4, 2); impl_valid_match!(CTIMER4, CTIMER4_CH3, P4_5, 3); } #[cfg(feature = "mcxa5xx")] mod mcxa5xx { use super::*; // CTIMER0 match channels impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_16, 0); #[cfg(feature = "swd-swo-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_2, 0); impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_22, 0); impl_valid_match!(CTIMER0, CTIMER0_CH0, P0_24, 0); impl_valid_match!(CTIMER0, CTIMER0_CH0, P2_12, 0); impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_17, 1); impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_23, 1); impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_25, 1); #[cfg(feature = "jtag-extras-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH1, P0_3, 1); impl_valid_match!(CTIMER0, CTIMER0_CH1, P2_13, 1); impl_valid_match!(CTIMER0, CTIMER0_CH2, P0_12, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P0_18, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P0_26, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P0_4, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P1_0, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P1_8, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P2_15, 2); impl_valid_match!(CTIMER0, CTIMER0_CH2, P2_16, 2); #[cfg(feature = "sosc-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH2, P3_30, 2); impl_valid_match!(CTIMER0, CTIMER0_CH3, P0_13, 3); impl_valid_match!(CTIMER0, CTIMER0_CH3, P0_19, 3); impl_valid_match!(CTIMER0, CTIMER0_CH3, P0_27, 3); impl_valid_match!(CTIMER0, CTIMER0_CH3, P0_5, 3); impl_valid_match!(CTIMER0, CTIMER0_CH3, P1_1, 3); impl_valid_match!(CTIMER0, CTIMER0_CH3, P1_9, 3); impl_valid_match!(CTIMER0, CTIMER0_CH3, P2_17, 3); #[cfg(feature = "sosc-as-gpio")] impl_valid_match!(CTIMER0, CTIMER0_CH3, P3_31, 3); // CTIMER1 match channels impl_valid_match!(CTIMER1, CTIMER1_CH0, P1_2, 0); impl_valid_match!(CTIMER1, CTIMER1_CH1, P1_3, 1); impl_valid_match!(CTIMER1, CTIMER1_CH2, P1_4, 2); impl_valid_match!(CTIMER1, CTIMER1_CH3, P1_5, 3); impl_valid_match!(CTIMER1, CTIMER1_CH0, P2_4, 0); impl_valid_match!(CTIMER1, CTIMER1_CH1, P2_5, 1); impl_valid_match!(CTIMER1, CTIMER1_CH2, P2_6, 2); impl_valid_match!(CTIMER1, CTIMER1_CH0, P3_10, 0); impl_valid_match!(CTIMER1, CTIMER1_CH1, P3_11, 1); impl_valid_match!(CTIMER1, CTIMER1_CH2, P3_12, 2); impl_valid_match!(CTIMER1, CTIMER1_CH3, P2_7, 3); impl_valid_match!(CTIMER1, CTIMER1_CH3, P3_13, 3); // CTIMER2 match channels impl_valid_match!(CTIMER2, CTIMER2_CH0, P1_10, 0); impl_valid_match!(CTIMER2, CTIMER2_CH0, P2_0, 0); impl_valid_match!(CTIMER2, CTIMER2_CH0, P2_20, 0); impl_valid_match!(CTIMER2, CTIMER2_CH0, P3_18, 0); impl_valid_match!(CTIMER2, CTIMER2_CH1, P1_11, 1); impl_valid_match!(CTIMER2, CTIMER2_CH1, P2_1, 1); impl_valid_match!(CTIMER2, CTIMER2_CH1, P2_21, 1); impl_valid_match!(CTIMER2, CTIMER2_CH1, P3_19, 1); impl_valid_match!(CTIMER2, CTIMER2_CH2, P1_12, 2); impl_valid_match!(CTIMER2, CTIMER2_CH2, P2_2, 2); impl_valid_match!(CTIMER2, CTIMER2_CH2, P2_22, 2); impl_valid_match!(CTIMER2, CTIMER2_CH2, P3_20, 2); impl_valid_match!(CTIMER2, CTIMER2_CH3, P1_13, 3); impl_valid_match!(CTIMER2, CTIMER2_CH3, P2_23, 3); impl_valid_match!(CTIMER2, CTIMER2_CH3, P2_3, 3); impl_valid_match!(CTIMER2, CTIMER2_CH3, P3_21, 3); // CTIMER3 match channels impl_valid_match!(CTIMER3, CTIMER3_CH0, P1_14, 0); impl_valid_match!(CTIMER3, CTIMER3_CH0, P1_18, 0); impl_valid_match!(CTIMER3, CTIMER3_CH0, P2_16, 0); impl_valid_match!(CTIMER3, CTIMER3_CH0, P2_8, 0); impl_valid_match!(CTIMER3, CTIMER3_CH1, P1_15, 1); impl_valid_match!(CTIMER3, CTIMER3_CH1, P1_19, 1); impl_valid_match!(CTIMER3, CTIMER3_CH1, P2_17, 1); impl_valid_match!(CTIMER3, CTIMER3_CH1, P2_9, 1); impl_valid_match!(CTIMER3, CTIMER3_CH1, P3_27, 1); impl_valid_match!(CTIMER3, CTIMER3_CH2, P2_10, 2); impl_valid_match!(CTIMER3, CTIMER3_CH2, P2_18, 2); impl_valid_match!(CTIMER3, CTIMER3_CH2, P3_28, 2); impl_valid_match!(CTIMER3, CTIMER3_CH3, P2_11, 3); impl_valid_match!(CTIMER3, CTIMER3_CH3, P2_19, 3); #[cfg(feature = "dangerous-reset-as-gpio")] impl_valid_match!(CTIMER3, CTIMER3_CH3, P3_29, 3); // CTIMER4 match channels impl_valid_match!(CTIMER4, CTIMER4_CH0, P1_6, 0); impl_valid_match!(CTIMER4, CTIMER4_CH0, P2_12, 0); impl_valid_match!(CTIMER4, CTIMER4_CH0, P3_2, 0); impl_valid_match!(CTIMER4, CTIMER4_CH0, P4_2, 0); impl_valid_match!(CTIMER4, CTIMER4_CH1, P1_7, 1); impl_valid_match!(CTIMER4, CTIMER4_CH1, P2_13, 1); impl_valid_match!(CTIMER4, CTIMER4_CH1, P3_3, 1); impl_valid_match!(CTIMER4, CTIMER4_CH1, P4_3, 1); impl_valid_match!(CTIMER4, CTIMER4_CH2, P2_14, 2); impl_valid_match!(CTIMER4, CTIMER4_CH2, P3_6, 2); impl_valid_match!(CTIMER4, CTIMER4_CH2, P4_4, 2); impl_valid_match!(CTIMER4, CTIMER4_CH3, P2_15, 3); impl_valid_match!(CTIMER4, CTIMER4_CH3, P3_7, 3); impl_valid_match!(CTIMER4, CTIMER4_CH3, P4_5, 3); } ================================================ FILE: embassy-mcxa/src/dma.rs ================================================ //! DMA driver. //! //! This module provides a typed channel abstraction over the EDMA_0_TCD0 array //! and helpers for configuring the channel MUX. The driver supports //! higher-level async transfer APIs. //! //! # Architecture //! //! The MCXA276 has 8 DMA channels (0-7), each with its own interrupt vector. //! Each channel has a Transfer Control Descriptor (TCD) that defines the //! transfer parameters. //! //! # Choosing the Right API //! //! This module provides several API levels to match different use cases: //! //! ## High-Level Async API (Recommended for Most Users) //! //! Use the async methods when you want simple, safe DMA transfers: //! //! | Method | Description | //! |--------|-------------| //! | [`DmaChannel::mem_to_mem()`] | Memory-to-memory copy | //! | [`DmaChannel::memset()`] | Fill memory with a pattern | //! | [`DmaChannel::write_to_peripheral()`] | Memory-to-peripheral (TX) | //! | [`DmaChannel::read_from_peripheral()`] | Peripheral-to-memory (RX) | //! //! These return a [`Transfer`] future that can be `.await`ed: //! //! ```rust,no_run //! #![no_std] //! #![no_main] //! //! # extern crate panic_halt; //! # extern crate embassy_mcxa; //! # extern crate embassy_executor; //! # use panic_halt as _; //! use embassy_executor::Spawner; //! use embassy_mcxa::clocks::config::Div8; //! use embassy_mcxa::config::Config; //! use embassy_mcxa::dma::{DmaChannel, TransferOptions}; //! //! #[embassy_executor::main] //! async fn main(_spawner: Spawner) { //! let mut config = Config::default(); //! config.clock_cfg.sirc.fro_12m_enabled = true; //! config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); //! //! let p = embassy_mcxa::init(config); //! //! let src = [0xa5u8; 4]; //! let mut dst = [0u8; 4]; //! //! let mut ch0 = DmaChannel::new(p.DMA_CH0); //! let options = TransferOptions::COMPLETE_INTERRUPT; //! let transfer = ch0.mem_to_mem(&src, &mut dst, options).unwrap(); //! transfer.await; //! } //! ``` //! //! ## Scatter-Gather Builder (For Chained Transfers) //! //! Use [`ScatterGatherBuilder`] for complex multi-segment transfers: //! //! ```rust,no_run //! #![no_std] //! #![no_main] //! //! # extern crate panic_halt; //! # extern crate embassy_mcxa; //! # extern crate embassy_executor; //! # use panic_halt as _; //! use embassy_executor::Spawner; //! use embassy_mcxa::clocks::config::Div8; //! use embassy_mcxa::config::Config; //! use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; //! //! #[embassy_executor::main] //! async fn main(_spawner: Spawner) { //! let mut config = Config::default(); //! config.clock_cfg.sirc.fro_12m_enabled = true; //! config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); //! //! let p = embassy_mcxa::init(config); //! //! let src1 = [0x55u8; 4]; //! let src2 = [0xaau8; 4]; //! let src3 = [0x5au8; 4]; //! let mut dst1 = [0u8; 4]; //! let mut dst2 = [0u8; 4]; //! let mut dst3 = [0u8; 4]; //! //! let mut ch0 = DmaChannel::new(p.DMA_CH0); //! let mut builder = ScatterGatherBuilder::::new(); //! //! builder.add_transfer(&src1, &mut dst1); //! builder.add_transfer(&src2, &mut dst2); //! builder.add_transfer(&src3, &mut dst3); //! //! let transfer = builder.build(ch0).unwrap(); //! transfer.blocking_wait(); //! } //! ``` #![allow(dead_code)] use core::future::Future; use core::marker::PhantomData; use core::pin::Pin; use core::ptr::NonNull; use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering, fence}; use core::task::{Context, Poll}; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::clocks::enable_and_reset; use crate::clocks::periph_helpers::NoConfig; use crate::dma::sealed::SealedChannel; use crate::pac::dma::vals::Halt; use crate::pac::edma_0_tcd::regs::{TcdAttr, TcdBiterElinkno, TcdCiterElinkno, TcdCsr}; use crate::pac::edma_0_tcd::vals::{ Bwc, Dpa, Dreq, Ecp, Esg, Size, Start, TcdNbytesMloffnoDmloe, TcdNbytesMloffnoSmloe, }; use crate::pac::{self, Interrupt}; use crate::peripherals::DMA0; /// Initialize DMA controller (clock enabled, reset released, controller configured). /// /// This function is intended to be called ONCE during HAL initialization (`hal::init()`). /// /// The function enables the DMA0 clock, releases reset, and configures the controller /// for normal operation with round-robin arbitration. pub(crate) fn init() { // Enable DMA0 clock and release reset let _ = unsafe { enable_and_reset::(&NoConfig) }; // Configure DMA controller pac::DMA0.mp_csr().modify(|w| { w.set_edbg(true); w.set_erca(true); w.set_halt(Halt::NORMAL_OPERATION); w.set_gclc(true); w.set_gmrc(true); }); // REVISIT: This needs to be improved. // // Enable all DMA request lines for non-secure access. #[cfg(feature = "mcxa5xx")] { let ahbsc = crate::pac::AHBSC; ahbsc.sec_gp_reg0().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg1().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg2().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg3().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg4().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg5().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg6().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg7().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg8().write(|w| w.0 = 0xffff_ffff); ahbsc.sec_gp_reg9().write(|w| w.0 = 0xffff_ffff); } } /// DMA transfer priority. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Priority { /// Highest priority. P0 = 0, P1 = 1, P2 = 2, P3 = 3, P4 = 4, P5 = 5, P6 = 6, /// Lowest priority. #[default] P7 = 7, } impl Priority { pub const LOWEST: Priority = Priority::P7; pub const HIGHEST: Priority = Priority::P0; } /// DMA transfer data width. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum WordSize { /// 8-bit (1 byte) transfers. OneByte, /// 16-bit (2 byte) transfers. TwoBytes, /// 32-bit (4 byte) transfers. #[default] FourBytes, } impl WordSize { /// Largest [WordSize] supported by this HAL driver. pub const LARGEST: WordSize = WordSize::FourBytes; /// Size in bytes. pub const fn bytes(self) -> usize { match self { WordSize::OneByte => 1, WordSize::TwoBytes => 2, WordSize::FourBytes => 4, } } /// Convert to hardware SSIZE/DSIZE field value. pub const fn to_hw_size(self) -> Size { match self { WordSize::OneByte => Size::EIGHT_BIT, WordSize::TwoBytes => Size::SIXTEEN_BIT, WordSize::FourBytes => Size::THIRTYTWO_BIT, } } /// Create from byte width (1, 2, or 4). pub const fn from_bytes(bytes: u8) -> Option { match bytes { 1 => Some(WordSize::OneByte), 2 => Some(WordSize::TwoBytes), 4 => Some(WordSize::FourBytes), _ => None, } } } /// Trait for word-sizes that are supported. pub trait Word: Copy + 'static { /// The word size for this type. fn size() -> WordSize; } impl Word for u8 { fn size() -> WordSize { WordSize::OneByte } } impl Word for u16 { fn size() -> WordSize { WordSize::TwoBytes } } impl Word for u32 { fn size() -> WordSize { WordSize::FourBytes } } /// DMA transfer options. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct TransferOptions { /// Enable interrupt on half transfer complete. pub half_transfer_interrupt: bool, /// Enable interrupt on transfer complete. pub complete_transfer_interrupt: bool, /// Transfer priority pub priority: Priority, } impl TransferOptions { /// Short-hand to specify that no options should be configured. pub const NO_INTERRUPTS: Self = Self { half_transfer_interrupt: false, complete_transfer_interrupt: false, priority: Priority::LOWEST, }; /// Short-hand to specify that only the complete transfer interrupt should be triggered. pub const COMPLETE_INTERRUPT: Self = Self { half_transfer_interrupt: false, complete_transfer_interrupt: true, priority: Priority::LOWEST, }; } /// General DMA error types. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// The DMA controller reported a bus error. BusError, /// The transfer was aborted. Aborted, /// Configuration error (e.g., invalid parameters). Configuration, /// Buffer overrun (for ring buffers). Overrun, } /// An error that can occur if the parameters passed were invalid. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct InvalidParameters; /// Maximum bytes per DMA transfer (eDMA4 CITER/BITER are 15-bit fields). /// /// This is a hardware limitation of the eDMA4 controller. Transfers larger /// than this must be split into multiple DMA operations. pub const DMA_MAX_TRANSFER_SIZE: usize = 0x7FFF; /// DMA request sources /// /// (from MCXA266 reference manual PDF attachment "DMA_Configuration.xml") #[derive(Clone, Copy, Debug)] #[repr(u8)] #[allow(dead_code)] #[cfg(feature = "mcxa2xx")] pub(crate) enum DmaRequest { WUU0WakeUpEvent = 1, CAN0 = 2, LPI2C2Rx = 3, LPI2C2Tx = 4, LPI2C3Rx = 5, LPI2C3Tx = 6, I3C0Rx = 7, I3C0Tx = 8, LPI2C0Rx = 11, LPI2C0Tx = 12, LPI2C1Rx = 13, LPI2C1Tx = 14, LPSPI0Rx = 15, LPSPI0Tx = 16, LPSPI1Rx = 17, LPSPI1Tx = 18, LPUART0Rx = 21, LPUART0Tx = 22, LPUART1Rx = 23, LPUART1Tx = 24, LPUART2Rx = 25, LPUART2Tx = 26, LPUART3Rx = 27, LPUART3Tx = 28, LPUART4Rx = 29, LPUART4Tx = 30, Ctimer0M0 = 31, Ctimer0M1 = 32, Ctimer1M0 = 33, Ctimer1M1 = 34, Ctimer2M0 = 35, Ctimer2M1 = 36, Ctimer3M0 = 37, Ctimer3M1 = 38, Ctimer4M0 = 39, Ctimer4M1 = 40, FlexPWM0Capt0 = 41, FlexPWM0Capt1 = 42, FlexPWM0Capt2 = 43, FlexPWM0Capt3 = 44, FlexPWM0Val0 = 45, FlexPWM0Val1 = 46, FlexPWM0Val2 = 47, FlexPWM0Val3 = 48, LPTMR0CounterMatchEvent = 49, ADC0FifoRequest = 51, ADC1FifoRequest = 52, CMP0 = 53, CMP1 = 54, CMP2 = 55, DAC0FifoRequest = 56, GPIO0PinEvent0 = 60, GPIO1PinEvent0 = 61, GPIO2PinEvent0 = 62, GPIO3PinEvent0 = 63, GPIO4PinEvent0 = 64, QDC0 = 65, QDC1 = 66, FlexIO0SR0 = 71, FlexIO0SR1 = 72, FlexIO0SR2 = 73, FlexIO0SR3 = 74, FlexPWM1ReqCapt0 = 79, FlexPWM1ReqCapt1 = 80, FlexPWM1ReqCapt2 = 81, FlexPWM1ReqCapt3 = 82, FlexPWM1ReqVal0 = 83, FlexPWM1ReqVal1 = 84, FlexPWM1ReqVal2 = 85, FlexPWM1ReqVal3 = 86, CAN1 = 87, LPUART5Rx = 102, LPUART5Tx = 103, MAU0MAU = 115, SGI0ReqIdat = 119, SGI0ReqOdat = 120, ADC2FifoRequest = 123, ADC3FifoRequest = 124, } /// DMA request sources /// /// (from MCXA577 reference manual PDF attachment "DMA_Configuration.xml") #[derive(Clone, Copy, Debug)] #[repr(u8)] #[allow(dead_code)] #[cfg(feature = "mcxa5xx")] pub(crate) enum DmaRequest { WUU0WakeUpEvent = 1, CAN0 = 2, LPI2C2Rx = 3, LPI2C2Tx = 4, LPI2C3Rx = 5, LPI2C3Tx = 6, I3C0Rx = 7, I3C0Tx = 8, I3C1Rx = 9, I3C1Tx = 10, LPI2C0Rx = 11, LPI2C0Tx = 12, LPI2C1Rx = 13, LPI2C1Tx = 14, LPSPI0Rx = 15, LPSPI0Tx = 16, LPSPI1Rx = 17, LPSPI1Tx = 18, LPSPI2Rx = 19, LPSPI2Tx = 20, LPUART0Rx = 21, LPUART0Tx = 22, LPUART1Rx = 23, LPUART1Tx = 24, LPUART2Rx = 25, LPUART2Tx = 26, LPUART3Rx = 27, LPUART3Tx = 28, LPUART4Rx = 29, LPUART4Tx = 30, Ctimer0M0 = 31, Ctimer0M1 = 32, Ctimer1M0 = 33, Ctimer1M1 = 34, Ctimer2M0 = 35, Ctimer2M1 = 36, Ctimer3M0 = 37, Ctimer3M1 = 38, Ctimer4M0 = 39, Ctimer4M1 = 40, LPTMR0CounterMatchEvent = 49, ADC0FifoRequest = 51, ADC1FifoRequest = 52, CMP0 = 53, DAC0FifoRequest = 56, DAC1FifoRequest = 57, GPIO5PinEvent0 = 59, GPIO0PinEvent0 = 60, GPIO1PinEvent0 = 61, GPIO2PinEvent0 = 62, GPIO3PinEvent0 = 63, GPIO4PinEvent0 = 64, TsiEndOfScan = 69, TsiOutOfRange = 70, FlexIO0SR0 = 71, FlexIO0SR1 = 72, FlexIO0SR2 = 73, FlexIO0SR3 = 74, CAN1 = 87, EspiCh0 = 92, EspiCh1 = 93, LPI2C4Rx = 94, LPI2C4Tx = 95, LPSPI3Rx = 96, LPSPI3Tx = 97, LPSPI4Rx = 98, LPSPI4Tx = 99, LPSPI5Rx = 100, LPSPI5Tx = 101, LPUART5Rx = 102, LPUART5Tx = 103, I3C2Rx = 106, I3C2Tx = 107, I3C3Rx = 108, I3C3Tx = 109, FlexSPI0Rx = 110, FlexSPI0Tx = 111, ITRCTmprOut0 = 117, SGI0ReqIdat = 119, SGI0ReqOdat = 120, Gpio0PinEvent1 = 132, Gpio1PinEvent1 = 133, Gpio2PinEvent1 = 134, Gpio3PinEvent1 = 135, Gpio4PinEvent1 = 136, Gpio5PinEvent1 = 137, } impl DmaRequest { /// Convert enumerated value into a raw integer pub const fn number(self) -> u8 { self as u8 } /// Convert a raw integer into an enumerated value /// /// ## SAFETY /// /// The given number MUST be one of the defined variant, e.g. a number /// derived from [`Self::number()`], otherwise it is immediate undefined behavior. pub unsafe fn from_number_unchecked(num: u8) -> Self { unsafe { core::mem::transmute(num) } } } mod sealed { /// Sealed trait for DMA channels. pub trait SealedChannel { /// Zero-based channel index into the TCD array. fn index(&self) -> usize; /// Interrupt vector for this channel. fn interrupt(&self) -> crate::interrupt::Interrupt; } } /// Marker trait implemented by HAL peripheral tokens that map to a DMA0 /// channel backed by one EDMA_0_TCD0 TCD slot. #[allow(private_bounds)] pub trait Channel: sealed::SealedChannel + PeripheralType + Into + 'static {} /// Type-erased DMA channel peripheral. /// /// This allows storing DMA channels in a uniform way regardless of their /// concrete type, useful for async transfer futures and runtime channel selection. /// /// ```rust,no_run /// # #![no_std] /// # #![no_main] /// # /// # extern crate panic_halt; /// # extern crate embassy_mcxa; /// # extern crate embassy_executor; /// # use panic_halt as _; /// # use embassy_executor::Spawner; /// # use embassy_hal_internal::Peri; /// # use embassy_mcxa::clocks::config::Div8; /// # use embassy_mcxa::config::Config; /// # use embassy_mcxa::dma::{DmaChannel, AnyChannel}; /// # /// # #[embassy_executor::main] /// # async fn main(_spawner: Spawner) { /// # let mut config = Config::default(); /// # config.clock_cfg.sirc.fro_12m_enabled = true; /// # config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); /// # /// # let p = embassy_mcxa::init(config); /// let anychannel: Peri<'static, AnyChannel> = p.DMA_CH0.into(); /// DmaChannel::new(anychannel); /// # } /// ``` #[derive(Debug, Clone, Copy)] pub struct AnyChannel { index: usize, interrupt: Interrupt, } impl PeripheralType for AnyChannel {} impl sealed::SealedChannel for AnyChannel { fn index(&self) -> usize { self.index } fn interrupt(&self) -> Interrupt { self.interrupt } } impl Channel for AnyChannel {} /// Macro to implement Channel trait for a peripheral. macro_rules! impl_channel { ($peri:ident, $index:expr, $irq:ident) => { impl sealed::SealedChannel for crate::peripherals::$peri { fn index(&self) -> usize { $index } fn interrupt(&self) -> Interrupt { Interrupt::$irq } } impl Channel for crate::peripherals::$peri {} impl From for AnyChannel { fn from(_: crate::peripherals::$peri) -> Self { AnyChannel { index: $index, interrupt: Interrupt::$irq, } } } }; } impl_channel!(DMA_CH0, 0, DMA_CH0); impl_channel!(DMA_CH1, 1, DMA_CH1); impl_channel!(DMA_CH2, 2, DMA_CH2); impl_channel!(DMA_CH3, 3, DMA_CH3); impl_channel!(DMA_CH4, 4, DMA_CH4); impl_channel!(DMA_CH5, 5, DMA_CH5); impl_channel!(DMA_CH6, 6, DMA_CH6); impl_channel!(DMA_CH7, 7, DMA_CH7); /// Parameters used to configure a 'typical' DMA transfer in [DmaChannel::setup_typical]. struct DmaTransferParameters { /// Number of words (with size WDST) that should be transferred to the destination. dst_count: usize, /// Source pointer. If incrementing, the backing memory region should be at least as large as `count`. src_ptr: *const WSRC, /// Destination pointer. If incrementing, the backing memory region should be at least as large as `count`. dst_ptr: *mut WDST, /// Whether the source pointer should be incremented. src_incr: bool, /// Whether the destination pointer should be incremented. dst_incr: bool, /// Perform circular DMA. /// /// After each loop, will reset the current pointer to the starting addresses, both for src and dest. circular: bool, /// The transfer will be requested and managed by software. /// /// This implies that the transfer is completed in a single major loop iteration. /// The transfer is also started from software, instead of using target DMA peripheral request signal. software: bool, /// Public facing transfer options that might be relevant. options: TransferOptions, } /// DMA channel driver. pub struct DmaChannel<'a> { channel: Peri<'a, AnyChannel>, } impl<'a> DmaChannel<'a> { /// Wrap a DMA channel token (takes ownership of the Peri wrapper). /// /// Note: DMA is initialized during `hal::init()` via `dma::init()`. #[inline] pub fn new(channel: embassy_hal_internal::Peri<'a, C>) -> Self { unsafe { cortex_m::peripheral::NVIC::unmask(channel.interrupt()); } Self { channel: channel.into(), } } } impl DmaChannel<'_> { /// Reborrow the DmaChannel with a shorter lifetime. pub fn reborrow(&mut self) -> DmaChannel<'_> { DmaChannel { channel: self.channel.reborrow(), } } /// Channel index in the EDMA_0_TCD0 array. #[inline] pub(crate) fn index(&self) -> usize { self.channel.index() } /// Return a reference to the underlying TCD register block. #[inline] pub(crate) fn tcd(&self) -> pac::edma_0_tcd::Tcd { // Safety: MCXA276 has a single eDMA instance pac::EDMA_0_TCD0.tcd(self.channel.index()) } /// set a manual callback to be called AFTER the DMA interrupt is processed. Will be called in the DMA interrupt /// context. /// /// SAFETY: This must only be called on an owned DmaChannel, as there is only a single /// callback slot, and calling this will invalidate any previously set callbacks. pub(crate) unsafe fn set_callback(&mut self, f: fn()) { // See https://doc.rust-lang.org/std/primitive.fn.html#casting-to-and-from-integers let cb = f as *mut (); CALLBACKS[self.index()].store(cb, Ordering::Release); } /// Unset the callback, causing no method to be called after DMA completion. /// /// SAFETY: This must only be called on an owned DmaChannel, as there is only a single /// callback slot, and calling this will invalidate any previously set callbacks. pub(crate) unsafe fn clear_callback(&mut self) { CALLBACKS[self.index()].store(core::ptr::null_mut(), Ordering::Release); } /// Access TCD DADDR field pub(crate) fn daddr(&self) -> u32 { self.tcd().tcd_daddr().read().daddr() } fn clear_tcd(t: &pac::edma_0_tcd::Tcd) { // Full TCD reset following NXP SDK pattern (EDMA_TcdResetExt). // Reset ALL TCD registers to 0 to clear any stale configuration from // previous transfers. This is critical when reusing a channel. t.tcd_saddr().write(|w| w.set_saddr(0)); t.tcd_soff().write(|w| w.set_soff(0)); t.tcd_attr().write(|w| *w = TcdAttr(0)); t.tcd_nbytes_mloffno().write(|w| w.set_nbytes(0)); t.tcd_daddr().write(|w| w.set_daddr(0)); t.tcd_doff().write(|w| w.set_doff(0)); t.tcd_citer_elinkno().write(|w| *w = TcdCiterElinkno(0)); t.tcd_dlast_sga().write(|w| w.set_dlast_sga(0)); t.tcd_csr().write(|w| *w = TcdCsr(0)); // Clear CSR completly t.tcd_biter_elinkno().write(|w| *w = TcdBiterElinkno(0)); } #[inline] fn set_major_loop_ct_elinkno(t: &pac::edma_0_tcd::Tcd, count: u16) { t.tcd_biter_elinkno().write(|w| w.set_biter(count)); t.tcd_citer_elinkno().write(|w| w.set_citer(count)); } #[inline] fn set_major_loop_nbytes_without_minor(t: &pac::edma_0_tcd::Tcd, count: u32) { t.tcd_nbytes_mloffno().write(|w| { w.set_smloe(TcdNbytesMloffnoSmloe::OFFSET_NOT_APPLIED); w.set_dmloe(TcdNbytesMloffnoDmloe::OFFSET_NOT_APPLIED); w.set_nbytes(count) }); } #[inline] fn set_no_final_adjustments(t: &pac::edma_0_tcd::Tcd) { // No source/dest adjustment after major loop t.tcd_slast_sda().write(|w| w.set_slast_sda(0)); t.tcd_dlast_sga().write(|w| w.set_dlast_sga(0)); } #[inline] fn set_source_ptr(t: &pac::edma_0_tcd::Tcd, p: *const T) { t.tcd_saddr().write(|w| w.set_saddr(p as u32)); } #[inline] fn set_source_increment(t: &pac::edma_0_tcd::Tcd, sz: WordSize) { t.tcd_soff().write(|w| w.set_soff(sz.bytes() as u16)); } #[inline] fn set_source_fixed(t: &pac::edma_0_tcd::Tcd) { t.tcd_soff().write(|w| w.set_soff(0)); } #[inline] fn set_dest_ptr(t: &pac::edma_0_tcd::Tcd, p: *mut T) { t.tcd_daddr().write(|w| w.set_daddr(p as u32)); } #[inline] fn set_dest_increment(t: &pac::edma_0_tcd::Tcd, sz: WordSize) { t.tcd_doff().write(|w| w.set_doff(sz.bytes() as u16)); } #[inline] fn set_dest_fixed(t: &pac::edma_0_tcd::Tcd) { t.tcd_doff().write(|w| w.set_doff(0)); } #[inline] fn set_fixed_priority(t: &pac::edma_0_tcd::Tcd, p: Priority) { t.ch_pri().write(|w| { w.set_dpa(Dpa::SUSPEND); w.set_ecp(Ecp::SUSPEND); w.set_apl(p as u8); }); } #[inline] fn reset_channel_state(t: &pac::edma_0_tcd::Tcd) { // CSR: Resets to all zeroes (disabled), "done" is cleared by writing 1 t.ch_csr().write(|w| w.set_done(true)); // ES: Resets to all zeroes (disabled), "err" is cleared by writing 1 t.ch_es().write(|w| w.set_err(true)); // INT: Resets to all zeroes (disabled), "int" is cleared by writing 1 t.ch_int().write(|w| w.set_int(true)); } /// Start an async transfer. /// /// The channel must already be configured. This enables the channel /// request and returns a `Transfer` future that resolves when the /// DMA transfer completes. /// /// # Safety /// /// The caller must ensure the DMA channel has been properly configured /// and that source/destination buffers remain valid for the duration /// of the transfer. #[allow(unused)] pub(crate) unsafe fn start_transfer(&mut self) -> Transfer<'_> { // Clear any previous DONE/INT flags let t = self.tcd(); t.ch_csr().modify(|w| w.set_done(true)); t.ch_int().write(|w| w.set_int(true)); // Enable the channel request t.ch_csr().modify(|w| { w.set_erq(true); w.set_earq(true); }); Transfer::new(self.reborrow()) } /// Setup a typical DMA transfer. /// /// A minor loop iteration transfers to a single word before yielding to arbitrage. /// A major loop iteration transfers the entire buffer. /// /// # Safety /// /// Requires that the source/destination buffers remain valid for the duration /// of the transfer. unsafe fn setup_transfers(&self, params: DmaTransferParameters) { let byte_count = (params.dst_count as usize * WDST::size().bytes()) as u32; let t = self.tcd(); // Reset channel state - clear DONE, disable requests, clear errors Self::reset_channel_state(&t); // Memory & compiler barrier to ensure channel state is fully reset before touching TCD fence(Ordering::Release); Self::clear_tcd(&t); Self::set_source_ptr(&t, params.src_ptr); if params.src_incr { Self::set_source_increment(&t, WSRC::size()); } else { Self::set_source_fixed(&t); } Self::set_dest_ptr(&t, params.dst_ptr); if params.dst_incr { Self::set_dest_increment(&t, WDST::size()); } else { Self::set_dest_fixed(&t); } // Transfer attributes (size) t.tcd_attr().write(|w| { w.set_ssize(WSRC::size().to_hw_size()); w.set_dsize(WDST::size().to_hw_size()); }); let (minor, major) = if params.software { // When called from software, transfer all bytes in a single major loop iteration. (byte_count, 1) } else { // When driven by hardware, transfer a DST word per major loop iteration. // When the DMA channel requests (ERQ) is driven by a peripheral, this is desirable. (WDST::size().bytes() as u32, params.dst_count as u16) }; // Set the amount of bytes to be transferred per major loop iteration without minor loop offsets. Self::set_major_loop_nbytes_without_minor(&t, minor); Self::set_major_loop_ct_elinkno(&t, major); // Configure channel to be interruptable, to interrupt, with a set priority. Self::set_fixed_priority(&t, params.options.priority); if params.circular { let byte_diff = -(byte_count as i32); // Decrement the address pointers (if incrementing & not fixed). let byte_diff_reg = byte_diff as u32; // Cast as u32 so that it can be stored in the register. t.tcd_slast_sda() .write(|w| w.set_slast_sda(if params.src_incr { byte_diff_reg } else { 0 })); t.tcd_dlast_sga() .write(|w| w.set_dlast_sga(if params.dst_incr { byte_diff_reg } else { 0 })); } else { Self::set_no_final_adjustments(&t); } // Memory & compiler barrier before setting START fence(Ordering::Release); // Control/status: interrupt on major complete, start // Write this last after all other TCD registers are configured t.tcd_csr().write(|w| { w.set_intmajor(params.options.complete_transfer_interrupt); w.set_inthalf(params.options.half_transfer_interrupt); w.set_start(if params.software { Start::CHANNEL_STARTED } else { Start::CHANNEL_NOT_STARTED }); w.set_esg(Esg::NORMAL_FORMAT); w.set_majorelink(false); w.set_eeop(false); w.set_esda(false); w.set_bwc(Bwc::NO_STALL); w.set_dreq(if params.circular { Dreq::CHANNEL_NOT_AFFECTED // Don't clear ERQ on complete (circular) } else { Dreq::ERQ_FIELD_CLEAR // Auto-disable request after major loop }); }); // Memory & compiler barrier after setting START fence(Ordering::Release); } // ======================================================================== // Type-Safe Transfer Methods (Embassy-style API) // ======================================================================== /// Perform a memory-to-memory DMA transfer (simplified API). /// /// This is a type-safe wrapper that uses the `Word` trait to determine /// the correct transfer width automatically. Uses the global eDMA TCD /// register accessor internally. /// /// # Arguments /// /// * `src` - Source buffer /// * `dst` - Destination buffer (must be at least as large as src) /// * `options` - Transfer configuration options /// /// # Safety /// /// The source and destination buffers must remain valid for the /// duration of the transfer. pub fn mem_to_mem( &mut self, src: &[W], dst: &mut [W], options: TransferOptions, ) -> Result, InvalidParameters> { if src.is_empty() || src.len() > dst.len() || src.len() > DMA_MAX_TRANSFER_SIZE { return Err(InvalidParameters); } unsafe { self.setup_transfers(DmaTransferParameters { src_ptr: src.as_ptr(), dst_ptr: dst.as_mut_ptr(), dst_count: dst.len(), src_incr: true, dst_incr: true, circular: false, software: true, options, }); } Ok(Transfer::new(self.reborrow())) } /// Fill a memory buffer with a pattern value (memset). /// /// This performs a DMA transfer where the source address remains fixed /// (pattern value) while the destination address increments through the buffer. /// It's useful for quickly filling large memory regions with a constant value. /// /// # Arguments /// /// * `pattern` - Reference to the pattern value (will be read repeatedly) /// * `dst` - Destination buffer to fill /// * `options` - Transfer configuration options /// /// # Example /// /// ```rust,no_run /// # #![no_std] /// # #![no_main] /// /// # extern crate panic_halt; /// # extern crate embassy_mcxa; /// # extern crate embassy_executor; /// # use panic_halt as _; /// # use embassy_executor::Spawner; /// # use embassy_mcxa::clocks::config::Div8; /// # use embassy_mcxa::config::Config; /// # use embassy_mcxa::dma::{DmaChannel, TransferOptions}; /// /// # #[embassy_executor::main] /// # async fn main(_spawner: Spawner) { /// let mut config = Config::default(); /// config.clock_cfg.sirc.fro_12m_enabled = true; /// config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); /// /// let p = embassy_mcxa::init(config); /// /// let mut ch0 = DmaChannel::new(p.DMA_CH0); /// let pattern = 0xDEADBEEF_u32; /// let mut buffer = [0u32; 256]; /// /// unsafe { /// ch0.memset(&pattern, &mut buffer, TransferOptions::COMPLETE_INTERRUPT).unwrap().await; /// } /// # } /// ``` pub fn memset( &mut self, pattern: &W, dst: &mut [W], options: TransferOptions, ) -> Result, InvalidParameters> { if dst.is_empty() || dst.len() > DMA_MAX_TRANSFER_SIZE { return Err(InvalidParameters); } unsafe { self.setup_transfers(DmaTransferParameters { src_ptr: pattern, dst_ptr: dst.as_mut_ptr(), dst_count: dst.len(), src_incr: false, dst_incr: true, circular: false, software: true, options, }); } Ok(Transfer::new(self.reborrow())) } /// Write data from memory to a peripheral register. /// /// The destination address remains fixed (peripheral register) while /// the source address increments through the buffer. /// /// # Arguments /// /// * `buf` - Source buffer to write from /// * `peri_addr` - Peripheral register address /// * `options` - Transfer configuration options /// /// # Safety /// /// - The buffer must remain valid for the duration of the transfer. /// - The peripheral address must be valid for writes. pub unsafe fn write_to_peripheral( &mut self, buf: &[W], peri_addr: *mut W, options: TransferOptions, ) -> Result, InvalidParameters> { unsafe { self.setup_write_to_peripheral(buf, peri_addr, false, options)? }; Ok(Transfer::new(self.reborrow())) } /// Read data from a peripheral register to memory. /// /// The source address remains fixed (peripheral register) while /// the destination address increments through the buffer. /// /// # Arguments /// /// * `peri_addr` - Peripheral register address /// * `buf` - Destination buffer to read into /// * `options` - Transfer configuration options /// /// # Safety /// /// - The buffer must remain valid for the duration of the transfer. /// - The peripheral address must be valid for reads. pub unsafe fn read_from_peripheral( &mut self, peri_addr: *const W, buf: &mut [W], options: TransferOptions, ) -> Result, InvalidParameters> { unsafe { self.setup_read_from_peripheral(peri_addr, buf, false, options)? }; Ok(Transfer::new(self.reborrow())) } /// Configure a memory-to-peripheral DMA transfer without starting it. /// /// This configures the TCD for a memory-to-peripheral transfer but does NOT /// return a Transfer object. The caller is responsible for: /// 1. Enabling the peripheral's DMA request /// 2. Calling `enable_request()` to start the transfer /// 3. Polling `is_done()` or using interrupts to detect completion /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup /// /// Use this when you need manual control over the DMA lifecycle (e.g., in /// peripheral drivers that have their own completion polling). /// /// # Arguments /// /// * `peri_addr` - Peripheral register address /// * `enable_interrupt` - Whether to enable interrupt on completion /// /// # Safety /// /// - The buffer must remain valid for the duration of the transfer. /// - The peripheral address must be valid for writes. pub(crate) unsafe fn setup_write_zeros_to_peripheral( &self, count: usize, peri_addr: *mut W, options: TransferOptions, ) -> Result<(), InvalidParameters> { if count > DMA_MAX_TRANSFER_SIZE { return Err(InvalidParameters); } #[repr(C, align(4))] struct Zero([u8; WordSize::LARGEST.bytes()]); // Static mut so that this is allocated in RAM. static mut DUMMY: Zero = Zero([0u8; WordSize::LARGEST.bytes()]); unsafe { self.setup_transfers(DmaTransferParameters { // Unsafe: cast to W is safe as DUMMY is aligned and guaranteed to be as least as large. src_ptr: core::ptr::addr_of_mut!(DUMMY) as *const W, dst_ptr: peri_addr, dst_count: count, src_incr: false, dst_incr: false, circular: false, software: false, options, }); } Ok(()) } /// Produce the number of bytes transferred at the time of calling /// this function. pub fn transferred_bytes(&self) -> usize { critical_section::with(|_| { let t = self.tcd(); let biter = t.tcd_biter_elinkno().read().biter() as usize; let citer = t.tcd_citer_elinkno().read().citer() as usize; let minor = t.tcd_nbytes_mloffno().read().nbytes() as usize; (biter - citer) * minor }) } /// Configure a memory-to-peripheral DMA transfer without starting it. /// /// This configures the TCD for a memory-to-peripheral transfer but does NOT /// return a Transfer object. The caller is responsible for: /// 1. Enabling the peripheral's DMA request /// 2. Calling `enable_request()` to start the transfer /// 3. Polling `is_done()` or using interrupts to detect completion /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup /// /// Use this when you need manual control over the DMA lifecycle (e.g., in /// peripheral drivers that have their own completion polling). /// /// # Arguments /// /// * `buf` - Source buffer to write from /// * `peri_addr` - Peripheral register address /// * `software` - Use software start for the transfer; otherwise use hardware ERQ to drive the transfer. /// Should be `false` unless your peripheral does not support hardware ERQ. /// * `enable_interrupt` - Whether to enable interrupt on completion /// /// # Safety /// /// - The buffer must remain valid for the duration of the transfer. /// - The peripheral address must be valid for writes. pub(crate) unsafe fn setup_write_to_peripheral( &self, buf: &[WSRC], peri_addr: *mut WDST, software: bool, options: TransferOptions, ) -> Result<(), InvalidParameters> { if buf.is_empty() || buf.len() > DMA_MAX_TRANSFER_SIZE { return Err(InvalidParameters); } unsafe { self.setup_transfers(DmaTransferParameters { src_ptr: buf.as_ptr(), dst_ptr: peri_addr, dst_count: buf.len() * WSRC::size().bytes() / WDST::size().bytes(), src_incr: true, dst_incr: false, circular: false, software, options, }); } Ok(()) } /// Configure a peripheral-to-memory DMA transfer without starting it. /// /// This configures the TCD for a peripheral-to-memory transfer but does NOT /// return a Transfer object. The caller is responsible for: /// 1. Enabling the peripheral's DMA request /// 2. Calling `enable_request()` to start the transfer /// 3. Polling `is_done()` or using interrupts to detect completion /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup /// /// Use this when you need manual control over the DMA lifecycle (e.g., in /// peripheral drivers that have their own completion polling). /// /// # Arguments /// /// * `peri_addr` - Peripheral register address /// * `buf` - Destination buffer to read into /// * `software` - Use software start for the transfer; otherwise use hardware ERQ to drive the transfer. /// Should be `false` unless your peripheral does not support hardware ERQ. /// * `enable_interrupt` - Whether to enable interrupt on completion /// /// # Safety /// /// - The buffer must remain valid for the duration of the transfer. /// - The peripheral address must be valid for reads. pub(crate) unsafe fn setup_read_from_peripheral( &self, peri_addr: *const WSRC, buf: &mut [WDST], software: bool, options: TransferOptions, ) -> Result<(), InvalidParameters> { if buf.is_empty() || buf.len() > DMA_MAX_TRANSFER_SIZE { return Err(InvalidParameters); } unsafe { self.setup_transfers(DmaTransferParameters { src_ptr: peri_addr, dst_ptr: buf.as_mut_ptr(), dst_count: buf.len(), src_incr: false, dst_incr: true, circular: false, software, options, }); } Ok(()) } /// Configure the integrated channel MUX to use the given typed /// DMA request source (e.g., [`Lpuart2TxRequest`] or [`Lpuart2RxRequest`]). /// /// This is the type-safe version that uses marker types to ensure /// compile-time verification of request source validity. /// /// # Safety /// /// The channel must be properly configured before enabling requests. /// The caller must ensure the DMA request source matches the peripheral /// that will drive this channel. /// /// # Note /// /// The NXP SDK requires a two-step write sequence: first clear /// the mux to 0, then set the actual source. This is a hardware /// requirement on eDMA4 for the mux to properly latch. /// /// # Example /// /// ```rust,ignore /// use embassy_mcxa::dma::{DmaChannel, Lpuart2RxRequest}; /// /// unsafe { /// channel.set_request_source(Lpuart2RxRequest::REQUEST_NUMBER); /// } /// ``` #[inline] pub(crate) unsafe fn set_request_source(&self, source: DmaRequest) { // Two-step write per NXP SDK: clear to 0, then set actual source. self.tcd().ch_mux().write(|w| w.set_src(0)); cortex_m::asm::dsb(); // Ensure the clear completes before setting new source self.tcd().ch_mux().write(|w| w.set_src(source.number())); } /// Enable hardware requests for this channel (ERQ=1). /// /// # Safety /// /// The channel must be properly configured before enabling requests. pub(crate) unsafe fn enable_request(&self) { let t = self.tcd(); t.ch_csr().modify(|w| { w.set_erq(true); w.set_earq(true); }); } /// Disable hardware requests for this channel (ERQ=0). /// /// # Safety /// /// Disabling requests on an active transfer may leave the transfer incomplete. pub(crate) unsafe fn disable_request(&self) { let t = self.tcd(); t.ch_csr().modify(|w| { w.set_erq(false); w.set_earq(false); }); } /// Return true if the channel's DONE flag is set. pub(crate) fn is_done(&self) -> bool { let t = self.tcd(); t.ch_csr().read().done() } /// Clear the DONE flag for this channel. /// /// Uses modify to preserve other bits (especially ERQ) unlike write /// which would clear ERQ and halt an active transfer. /// /// # Safety /// /// Clearing DONE while a transfer is in progress may cause undefined behavior. pub(crate) unsafe fn clear_done(&self) { let t = self.tcd(); t.ch_csr().modify(|w| w.set_done(true)); } /// Clear the channel interrupt flag (CH_INT.INT). /// /// # Safety /// /// Must be called from the correct interrupt context or with interrupts disabled. pub(crate) unsafe fn clear_interrupt(&self) { let t = self.tcd(); t.ch_int().write(|w| w.set_int(true)); } /// Trigger a software start for this channel. /// /// # Safety /// /// The channel must be properly configured with a valid TCD before triggering. #[allow(unused)] pub(crate) unsafe fn trigger_start(&self) { let t = self.tcd(); t.tcd_csr().modify(|w| w.set_start(Start::CHANNEL_STARTED)); } /// Get the waker for this channel pub(crate) fn waker(&self) -> &'static AtomicWaker { &STATES[self.channel.index()].waker } /// Enable the interrupt for this channel in the NVIC. pub(crate) fn enable_interrupt(&self) { unsafe { cortex_m::peripheral::NVIC::unmask(self.channel.interrupt()); } } /// Enable Major Loop Linking. /// /// When the major loop completes, the hardware will trigger a service request /// on `link_ch`. /// /// # Arguments /// /// * `link_ch` - Target channel index (0-7) to link to /// /// # Safety /// /// The channel must be properly configured before setting up linking. #[allow(unused)] pub(crate) unsafe fn set_major_link(&self, link_ch: usize) { let t = self.tcd(); t.tcd_csr().modify(|w| { w.set_majorelink(true); w.set_majorlinkch(link_ch as u8) }); } /// Disable Major Loop Linking. /// /// Removes any major loop channel linking previously configured. /// /// # Safety /// /// The caller must ensure this doesn't disrupt an active transfer that /// depends on the linking. #[allow(unused)] pub(crate) unsafe fn clear_major_link(&self) { let t = self.tcd(); t.tcd_csr().modify(|w| w.set_majorelink(false)); } /// Enable Minor Loop Linking. /// /// After each minor loop, the hardware will trigger a service request /// on `link_ch`. /// /// # Arguments /// /// * `link_ch` - Target channel index (0-7) to link to /// /// # Note /// /// This rewrites CITER and BITER registers to the ELINKYES format. /// It preserves the current loop count. /// /// # Safety /// /// The channel must be properly configured before setting up linking. #[allow(unused)] pub(crate) unsafe fn set_minor_link(&self, link_ch: usize) { let t = self.tcd(); // Read current CITER (assuming ELINKNO format initially) let current_citer = t.tcd_citer_elinkno().read().citer(); let current_biter = t.tcd_biter_elinkno().read().biter(); // Write back using ELINKYES format t.tcd_citer_elinkyes().write(|w| { w.set_citer(current_citer); w.set_elink(true); w.set_linkch(link_ch as u8); }); t.tcd_biter_elinkyes().write(|w| { w.set_biter(current_biter); w.set_elink(true); w.set_linkch(link_ch as u8); }); } /// Disable Minor Loop Linking. /// /// Removes any minor loop channel linking previously configured. /// This rewrites CITER and BITER registers to the ELINKNO format, /// preserving the current loop count. /// /// # Safety /// /// The caller must ensure this doesn't disrupt an active transfer that /// depends on the linking. #[allow(unused)] pub(crate) unsafe fn clear_minor_link(&self) { let t = self.tcd(); // Read current CITER (could be in either format, but we only need the count) // Note: In ELINKYES format, citer is 9 bits; in ELINKNO, it's 15 bits. // We read from ELINKNO which will give us the combined value. let current_citer = t.tcd_citer_elinkno().read().citer(); let current_biter = t.tcd_biter_elinkno().read().biter(); // Write back using ELINKNO format (disabling link) t.tcd_citer_elinkno().write(|w| { w.set_citer(current_citer); w.set_elink(false); }); t.tcd_biter_elinkno().write(|w| { w.set_biter(current_biter); w.set_elink(false); }); } /// Load a TCD from memory into the hardware channel registers. /// /// This is useful for scatter/gather and ping-pong transfers where /// TCDs are prepared in RAM and then loaded into the hardware. /// /// # Safety /// /// - The TCD must be properly initialized. /// - The caller must ensure no concurrent access to the same channel. unsafe fn load_tcd(&self, tcd: &Tcd) { let t = self.tcd(); t.tcd_saddr().write(|w| w.set_saddr(tcd.saddr)); t.tcd_soff().write(|w| w.set_soff(tcd.soff as u16)); t.tcd_attr().write(|w| w.0 = tcd.attr); t.tcd_nbytes_mloffno().write(|w| w.set_nbytes(tcd.nbytes)); t.tcd_slast_sda().write(|w| w.set_slast_sda(tcd.slast as u32)); t.tcd_daddr().write(|w| w.set_daddr(tcd.daddr)); t.tcd_doff().write(|w| w.set_doff(tcd.doff as u16)); t.tcd_citer_elinkno().write(|w| w.set_citer(tcd.citer)); t.tcd_dlast_sga().write(|w| w.set_dlast_sga(tcd.dlast_sga as u32)); t.tcd_csr().write(|w| w.0 = tcd.csr); t.tcd_biter_elinkno().write(|w| w.set_biter(tcd.biter)); } } /// In-memory representation of a Transfer Control Descriptor (TCD). /// /// This matches the hardware layout (32 bytes). #[repr(C, align(32))] #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] struct Tcd { pub saddr: u32, pub soff: i16, pub attr: u16, pub nbytes: u32, pub slast: i32, pub daddr: u32, pub doff: i16, pub citer: u16, pub dlast_sga: i32, pub csr: u16, pub biter: u16, } struct State { /// Waker for transfer complete interrupt waker: AtomicWaker, /// Waker for half-transfer interrupt half_waker: AtomicWaker, } impl State { const fn new() -> Self { Self { waker: AtomicWaker::new(), half_waker: AtomicWaker::new(), } } } static STATES: [State; 8] = [ State::new(), State::new(), State::new(), State::new(), State::new(), State::new(), State::new(), State::new(), ]; pub(crate) fn waker(idx: usize) -> &'static AtomicWaker { &STATES[idx].waker } pub(crate) fn half_waker(idx: usize) -> &'static AtomicWaker { &STATES[idx].half_waker } // ============================================================================ // Async Transfer Future // ============================================================================ /// An in-progress DMA transfer. /// /// This type implements `Future` and can be `.await`ed to wait for the /// transfer to complete. Dropping the transfer will abort it. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { channel: DmaChannel<'a>, } impl<'a> Transfer<'a> { /// Create a new transfer for the given channel. /// /// The caller must have already configured and started the DMA channel. pub(crate) fn new(channel: DmaChannel<'a>) -> Self { Self { channel } } /// Check if the transfer is still running. pub fn is_running(&self) -> bool { !self.channel.is_done() } /// Get the remaining transfer count. pub fn remaining(&self) -> u16 { let t = self.channel.tcd(); t.tcd_citer_elinkno().read().citer() } /// Block until the transfer completes. pub fn blocking_wait(self) { while self.is_running() { core::hint::spin_loop(); } // Ensure all DMA writes are visible fence(Ordering::Release); // Don't run drop (which would abort) core::mem::forget(self); } /// Wait for the half-transfer interrupt asynchronously. /// /// This is useful for double-buffering scenarios where you want to process /// the first half of the buffer while the second half is being filled. /// /// Returns `true` if the half-transfer occurred, `false` if the transfer /// completed before the half-transfer interrupt. /// /// # Note /// /// The transfer must be configured with `TransferOptions::half_transfer_interrupt = true` /// for this method to work correctly. pub async fn wait_half(&mut self) -> Result { use core::future::poll_fn; poll_fn(|cx| { let state = &STATES[self.channel.index()]; // Register the half-transfer waker state.half_waker.register(cx.waker()); // Check if there's an error let t = self.channel.tcd(); let es = t.ch_es().read(); if es.err() { // Currently, all error fields are in the lowest 8 bits, as-casting truncates let errs = es.0 as u8; return Poll::Ready(Err(TransferErrors(errs))); } // Check if we're past the half-way point let biter = t.tcd_biter_elinkno().read().biter(); let citer = t.tcd_citer_elinkno().read().citer(); let half_point = biter / 2; if self.channel.is_done() { // Transfer completed before half-transfer Poll::Ready(Ok(false)) } else if citer <= half_point { // We're past the half-way point fence(Ordering::SeqCst); Poll::Ready(Ok(true)) } else { Poll::Pending } }) .await } /// Abort the transfer. fn abort(&mut self) { let t = self.channel.tcd(); // Disable channel requests t.ch_csr().modify(|w| { w.set_erq(false); w.set_earq(false); }); // Clear any pending interrupt t.ch_int().write(|w| w.set_int(true)); // Clear DONE flag t.ch_csr().modify(|w| w.set_done(true)); fence(Ordering::SeqCst); } } /// A collection of [TransferError] returned by any transfer. /// /// Each error variant can be queried separately, or all errors can be iterated by using [TransferErrors::into_iter]. #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Copy, Clone, Debug)] pub struct TransferErrors(u8); /// Iterator to extract all [TransferError]s using [TransferErrors::into_iter]. #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Copy, Clone, Debug)] pub struct TransferErrorIter(u8); impl TransferErrors { const MAP: &[(u8, TransferError)] = &[ (1 << 0, TransferError::DestinationBus), (1 << 1, TransferError::SourceBus), (1 << 2, TransferError::ScatterGatherConfiguration), (1 << 3, TransferError::NbytesCiterConfiguration), (1 << 4, TransferError::DestinationOffset), (1 << 5, TransferError::DestinationAddress), (1 << 6, TransferError::SourceOffset), (1 << 7, TransferError::SourceAddress), ]; /// Destination Bus Error #[inline] pub fn has_destination_bus_err(&self) -> bool { (self.0 & (1 << 0)) != 0 } /// Source Bus Error #[inline] pub fn has_source_bus_err(&self) -> bool { (self.0 & (1 << 1)) != 0 } /// Indicates that `TCDn_DLAST_SGA` is not on a 32-byte boundary. This field is /// checked at the beginning of a scatter/gather operation after major loop completion /// if `TCDn_CSR[ESG]` is enabled. #[inline] pub fn has_scatter_gather_configuration_err(&self) -> bool { (self.0 & (1 << 2)) != 0 } /// This error indicates that one of the following has occurred: /// /// * `TCDn_NBYTES` is not a multiple of `TCDn_ATTR[SSIZE]` and `TCDn_ATTR[DSIZE]` /// * `TCDn_CITER[CITER]` is equal to zero /// * `TCDn_CITER[ELINK]` is not equal to `TCDn_BITER[ELINK]` #[inline] pub fn has_nbytes_citer_configuration_err(&self) -> bool { (self.0 & (1 << 3)) != 0 } /// `TCDn_DOFF` is inconsistent with `TCDn_ATTR[DSIZE]`. #[inline] pub fn has_destination_offset_err(&self) -> bool { (self.0 & (1 << 4)) != 0 } /// `TCDn_DADDR` is inconsistent with `TCDn_ATTR[DSIZE]`. #[inline] pub fn has_destination_address_err(&self) -> bool { (self.0 & (1 << 5)) != 0 } /// `TCDn_SOFF` is inconsistent with `TCDn_ATTR[SSIZE]`. #[inline] pub fn has_source_offset_err(&self) -> bool { (self.0 & (1 << 6)) != 0 } /// `TCDn_SADDR` is inconsistent with `TCDn_ATTR[SSIZE]` #[inline] pub fn has_source_address_err(&self) -> bool { (self.0 & (1 << 7)) != 0 } } impl IntoIterator for TransferErrors { type Item = TransferError; type IntoIter = TransferErrorIter; fn into_iter(self) -> Self::IntoIter { TransferErrorIter(self.0) } } impl Iterator for TransferErrorIter { type Item = TransferError; fn next(&mut self) -> Option { if self.0 == 0 { return None; } for (mask, var) in TransferErrors::MAP { // If the bit is set... if self.0 & mask != 0 { // clear the bit self.0 &= !mask; // and return the answer return Some(*var); } } // Shouldn't happen, but oh well. None } } /// An error that can be returned as the result of a failed transfer. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum TransferError { /// `TCDn_SADDR` is inconsistent with `TCDn_ATTR[SSIZE]` SourceAddress, /// `TCDn_SOFF` is inconsistent with `TCDn_ATTR[SSIZE]`. SourceOffset, /// `TCDn_DADDR` is inconsistent with `TCDn_ATTR[DSIZE]`. DestinationAddress, /// `TCDn_DOFF` is inconsistent with `TCDn_ATTR[DSIZE]`. DestinationOffset, /// This error indicates that one of the following has occurred: /// /// * `TCDn_NBYTES` is not a multiple of `TCDn_ATTR[SSIZE]` and `TCDn_ATTR[DSIZE]` /// * `TCDn_CITER[CITER]` is equal to zero /// * `TCDn_CITER[ELINK]` is not equal to `TCDn_BITER[ELINK]` NbytesCiterConfiguration, /// Indicates that `TCDn_DLAST_SGA` is not on a 32-byte boundary. This field is /// checked at the beginning of a scatter/gather operation after major loop completion /// if `TCDn_CSR[ESG]` is enabled. ScatterGatherConfiguration, /// Source Bus Error SourceBus, /// Destination Bus Error DestinationBus, } impl<'a> Unpin for Transfer<'a> {} impl<'a> Future for Transfer<'a> { type Output = Result<(), TransferErrors>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let state = &STATES[self.channel.index()]; state.waker.register(cx.waker()); if self.channel.is_done() { // Ensure all DMA writes are visible before returning fence(Ordering::Acquire); let es = self.channel.tcd().ch_es().read(); if es.err() { // Currently, all error fields are in the lowest 8 bits, as-casting truncates let errs = es.0 as u8; Poll::Ready(Err(TransferErrors(errs))) } else { Poll::Ready(Ok(())) } } else { Poll::Pending } } } impl<'a> Drop for Transfer<'a> { fn drop(&mut self) { // Only abort if the transfer is still running // If already complete, no need to abort if self.is_running() { self.abort(); // Wait for abort to complete while self.is_running() { core::hint::spin_loop(); } } fence(Ordering::Release); } } /// A ring buffer for continuous DMA reception. /// /// Can only be constructed by drivers in this HAL, and not from the application. /// /// This structure manages a circular DMA transfer, allowing continuous /// reception of data without losing bytes between reads. It uses both /// half-transfer and complete-transfer interrupts to track available data. pub struct RingBuffer<'channel, 'buf, W: Word> { /// Reference to the DmaChannel for the duration of the DMA transfer. /// /// When this RingBuffer is dropped, the DmaChannel becomes usable again. channel: DmaChannel<'channel>, /// Buffer pointer. We use NonNull instead of &mut because DMA acts like /// a separate thread writing to this buffer, and &mut claims exclusive /// access which the compiler could optimize incorrectly. buf: NonNull<[W]>, /// Buffer length cached for convenience buf_len: usize, /// Read position in the buffer (consumer side) read_pos: AtomicUsize, /// Phantom data to tie the lifetime to the original buffer _lt: PhantomData<&'buf mut [W]>, } impl<'channel, 'buf, W: Word> RingBuffer<'channel, 'buf, W> { /// Create a new ring buffer for the given channel and buffer. /// /// # Safety /// /// The caller must ensure: /// - The DMA channel has been configured for circular transfer /// - The buffer remains valid for the lifetime of the ring buffer /// - Only one RingBuffer exists per DMA channel at a time pub(crate) unsafe fn new(channel: DmaChannel<'channel>, buf: &'buf mut [W]) -> Self { let buf_len = buf.len(); Self { channel, buf: NonNull::from(buf), buf_len, read_pos: AtomicUsize::new(0), _lt: PhantomData, } } /// Get a slice reference to the buffer. /// /// # Safety /// /// The caller must ensure that DMA is not actively writing to the /// portion of the buffer being accessed, or that the access is /// appropriately synchronized. #[inline] unsafe fn buf_slice(&self) -> &[W] { unsafe { self.buf.as_ref() } } /// Get the current DMA write position in the buffer. /// /// This reads the current destination address from the DMA controller /// and calculates the buffer offset. fn dma_write_pos(&self) -> usize { let t = self.channel.tcd(); let daddr = t.tcd_daddr().read().daddr() as usize; let buf_start = self.buf.as_ptr() as *const W as usize; // Calculate offset from buffer start let offset = daddr.wrapping_sub(buf_start) / core::mem::size_of::(); // Ensure we're within bounds (DMA wraps around) offset % self.buf_len } /// Returns the number of bytes available to read. pub fn available(&self) -> usize { let write_pos = self.dma_write_pos(); let read_pos = self.read_pos.load(Ordering::Acquire); if write_pos >= read_pos { write_pos - read_pos } else { self.buf_len - read_pos + write_pos } } /// Check if the buffer has overrun (data was lost). /// /// This happens when DMA writes faster than the application reads. pub fn is_overrun(&self) -> bool { // In a true overrun, the DMA would have wrapped around and caught up // to our read position. We can detect this by checking if available() // equals the full buffer size (minus 1 to distinguish from empty). self.available() >= self.buf_len - 1 } /// Read data from the ring buffer into the provided slice. /// /// Returns the number of elements read, which may be less than /// `dst.len()` if not enough data is available. /// /// This method does not block; use `read_async()` for async waiting. pub fn read_immediate(&self, dst: &mut [W]) -> usize { let write_pos = self.dma_write_pos(); let read_pos = self.read_pos.load(Ordering::Acquire); // Calculate available bytes let available = if write_pos >= read_pos { write_pos - read_pos } else { self.buf_len - read_pos + write_pos }; let to_read = dst.len().min(available); if to_read == 0 { return 0; } // Safety: We only read from portions of the buffer that DMA has // already written to (between read_pos and write_pos). let buf = unsafe { self.buf_slice() }; // Read data, handling wrap-around let first_chunk = (self.buf_len - read_pos).min(to_read); dst[..first_chunk].copy_from_slice(&buf[read_pos..read_pos + first_chunk]); if to_read > first_chunk { let second_chunk = to_read - first_chunk; dst[first_chunk..to_read].copy_from_slice(&buf[..second_chunk]); } // Update read position let new_read_pos = (read_pos + to_read) % self.buf_len; self.read_pos.store(new_read_pos, Ordering::Release); to_read } /// Read data from the ring buffer asynchronously. /// /// This waits until at least one byte is available, then reads as much /// as possible into the destination buffer. /// /// Returns the number of elements read. pub async fn read(&self, dst: &mut [W]) -> Result { use core::future::poll_fn; if dst.is_empty() { return Ok(0); } poll_fn(|cx| { // Check for overrun if self.is_overrun() { return Poll::Ready(Err(Error::Overrun)); } // Try to read immediately let n = self.read_immediate(dst); if n > 0 { return Poll::Ready(Ok(n)); } // Register wakers for both half and complete interrupts let state = &STATES[self.channel.index()]; state.waker.register(cx.waker()); state.half_waker.register(cx.waker()); // Check again after registering waker (avoid race) let n = self.read_immediate(dst); if n > 0 { return Poll::Ready(Ok(n)); } Poll::Pending }) .await } /// Clear the ring buffer, discarding all unread data. pub fn clear(&self) { let write_pos = self.dma_write_pos(); self.read_pos.store(write_pos, Ordering::Release); } /// Stop the DMA transfer and consume the ring buffer. /// /// Returns any remaining unread data count. pub fn stop(mut self) -> usize { let res = self.teardown(); drop(self); res } /// Enable the DMA channel request. /// /// Call this to start continuous reception. /// This is separated from setup to allow for any additional configuration /// before starting the transfer. /// /// ## SAFETY /// /// The Dma Channel must have been setup with proper manual configuration prior to /// calling `enable_dma_request`. See safety requirements of the configuration methods /// for more details. pub(crate) unsafe fn enable_dma_request(&self) { unsafe { self.channel.enable_request(); } } /// Stop the DMA transfer. Intended to be called by `stop()` or `Drop`. fn teardown(&mut self) -> usize { let available = self.available(); // Disable the channel let t = self.channel.tcd(); t.ch_csr().modify(|w| { w.set_erq(false); w.set_earq(false) }); // Clear flags t.ch_int().write(|w| w.set_int(true)); t.ch_csr().modify(|w| w.set_done(true)); fence(Ordering::Release); available } } impl Drop for RingBuffer<'_, '_, W> { fn drop(&mut self) { self.teardown(); } } impl<'a> DmaChannel<'a> { /// Set up a circular DMA transfer for continuous peripheral-to-memory reception. /// /// This configures the DMA channel for circular operation with both half-transfer /// and complete-transfer interrupts enabled. The transfer runs continuously until /// stopped via [`RingBuffer::stop()`]. /// /// # Arguments /// /// * `peri_addr` - Peripheral register address to read from /// * `buf` - Destination buffer (should be power-of-2 size for best efficiency) /// /// # Returns /// /// A [`RingBuffer`] that can be used to read received data. /// /// # Safety /// /// - The peripheral address must be valid for reads. /// - The peripheral's DMA request must be configured to trigger this channel. pub(crate) unsafe fn setup_circular_read<'buf, W: Word>( &mut self, peri_addr: *const W, buf: &'buf mut [W], ) -> Result, InvalidParameters> { if buf.is_empty() || buf.len() > DMA_MAX_TRANSFER_SIZE { return Err(InvalidParameters); } unsafe { self.setup_transfers(DmaTransferParameters { src_ptr: peri_addr, dst_ptr: buf.as_mut_ptr(), dst_count: buf.len(), src_incr: false, dst_incr: true, circular: true, software: true, options: TransferOptions { half_transfer_interrupt: true, complete_transfer_interrupt: true, priority: Priority::default(), }, }); } // Enable NVIC interrupt for this channel so async wakeups work self.enable_interrupt(); Ok(unsafe { RingBuffer::new(self.reborrow(), buf) }) } } /// Maximum number of TCDs in a scatter-gather chain. pub(crate) const MAX_SCATTER_GATHER_TCDS: usize = 16; /// A builder for constructing scatter-gather DMA transfer chains. /// /// This provides a type-safe way to build TCD chains for scatter-gather /// transfers without manual TCD manipulation. /// /// # Example /// /// ```rust,no_run /// # #![no_std] /// # #![no_main] /// # /// # extern crate panic_halt; /// # extern crate embassy_mcxa; /// # extern crate embassy_executor; /// # use panic_halt as _; /// # use embassy_executor::Spawner; /// # use embassy_hal_internal::Peri; /// # use embassy_mcxa::clocks::config::Div8; /// # use embassy_mcxa::config::Config; /// # use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; /// # /// # #[embassy_executor::main] /// # async fn main(_spawner: Spawner) { /// # let mut config = Config::default(); /// # config.clock_cfg.sirc.fro_12m_enabled = true; /// # config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); /// # /// # let p = embassy_mcxa::init(config); /// let src1 = [0x00u32; 128]; /// let src2 = [0xffu32; 128]; /// let src3 = [0x55u32; 128]; /// /// let mut dst1 = [0x00u32; 128]; /// let mut dst2 = [0x00u32; 128]; /// let mut dst3 = [0x00u32; 128]; /// /// let mut ch0 = DmaChannel::new(p.DMA_CH0); /// let mut builder = ScatterGatherBuilder::::new(); /// /// // Add transfer segments /// builder.add_transfer(&src1, &mut dst1); /// builder.add_transfer(&src2, &mut dst2); /// builder.add_transfer(&src3, &mut dst3); /// /// // Build and execute /// let transfer = unsafe { builder.build(ch0).unwrap() }; /// transfer.await; /// # } /// ``` pub struct ScatterGatherBuilder<'a, W: Word> { /// TCD pool (must be 32-byte aligned) tcds: [Tcd; MAX_SCATTER_GATHER_TCDS], /// Number of TCDs configured count: usize, /// Phantom marker for word type _phantom: core::marker::PhantomData, _plt: core::marker::PhantomData<&'a mut W>, } impl<'a, W: Word> ScatterGatherBuilder<'a, W> { /// Create a new scatter-gather builder. pub fn new() -> Self { ScatterGatherBuilder { tcds: [Tcd::default(); MAX_SCATTER_GATHER_TCDS], count: 0, _phantom: core::marker::PhantomData, _plt: core::marker::PhantomData, } } /// Add a memory-to-memory transfer segment to the chain. /// /// # Arguments /// /// * `src` - Source buffer for this segment /// * `dst` - Destination buffer for this segment /// /// # Panics /// /// Panics if the maximum number of segments (16) is exceeded. pub fn add_transfer<'b: 'a>(&mut self, src: &'b [W], dst: &'b mut [W]) -> &mut Self { assert!(self.count < MAX_SCATTER_GATHER_TCDS, "Too many scatter-gather segments"); assert!(!src.is_empty()); assert!(dst.len() >= src.len()); let size = W::size(); let byte_size = size.bytes(); let hw_size = size.to_hw_size(); let nbytes = (src.len() * byte_size) as u32; // Build the TCD for this segment self.tcds[self.count] = Tcd { saddr: src.as_ptr() as u32, soff: byte_size as i16, attr: ((hw_size as u16) << 8) | (hw_size as u16), // SSIZE | DSIZE nbytes, slast: 0, daddr: dst.as_mut_ptr() as u32, doff: byte_size as i16, citer: 1, dlast_sga: 0, // Will be filled in by build() csr: 0x0002, // INTMAJOR only (ESG will be set for non-last TCDs) biter: 1, }; self.count += 1; self } /// Get the number of transfer segments added. pub fn segment_count(&self) -> usize { self.count } /// Build the scatter-gather chain and start the transfer. /// /// # Arguments /// /// * `channel` - The DMA channel to use for the transfer /// /// # Returns /// /// A `Transfer` future that completes when the entire chain has executed. pub fn build(&mut self, channel: DmaChannel<'a>) -> Result, Error> { if self.count == 0 { return Err(Error::Configuration); } // Link TCDs together // // CSR bit definitions: // - START = bit 0 = 0x0001 (triggers transfer when set) // - INTMAJOR = bit 1 = 0x0002 (interrupt on major loop complete) // - ESG = bit 4 = 0x0010 (enable scatter-gather, loads next TCD on complete) // // When hardware loads a TCD via scatter-gather (ESG), it copies the TCD's // CSR directly into the hardware register. If START is not set in that CSR, // the hardware will NOT auto-execute the loaded TCD. // // Strategy: // - First TCD: ESG | INTMAJOR (no START - we add it manually after loading) // - Middle TCDs: ESG | INTMAJOR | START (auto-execute when loaded via S/G) // - Last TCD: INTMAJOR | START (auto-execute, no further linking) for i in 0..self.count { let is_first = i == 0; let is_last = i == self.count - 1; if is_first { if is_last { // Only one TCD - no ESG, no START (we add START manually) self.tcds[i].dlast_sga = 0; self.tcds[i].csr = 0x0002; // INTMAJOR only } else { // First of multiple - ESG to link, no START (we add START manually) self.tcds[i].dlast_sga = &self.tcds[i + 1] as *const Tcd as i32; self.tcds[i].csr = 0x0012; // ESG | INTMAJOR } } else if is_last { // Last TCD (not first) - no ESG, but START so it auto-executes self.tcds[i].dlast_sga = 0; self.tcds[i].csr = 0x0003; // INTMAJOR | START } else { // Middle TCD - ESG to link, and START so it auto-executes self.tcds[i].dlast_sga = &self.tcds[i + 1] as *const Tcd as i32; self.tcds[i].csr = 0x0013; // ESG | INTMAJOR | START } } let t = channel.tcd(); // Reset channel state - clear DONE, disable requests, clear errors // This ensures the channel is in a clean state before loading the TCD DmaChannel::reset_channel_state(&t); // Memory barrier to ensure channel state is reset before loading TCD cortex_m::asm::dsb(); // Load first TCD into hardware unsafe { channel.load_tcd(&self.tcds[0]); } // Memory barrier before setting START cortex_m::asm::dsb(); // Start the transfer t.tcd_csr().modify(|w| w.set_start(Start::CHANNEL_STARTED)); Ok(Transfer::new(channel)) } /// Reset the builder for reuse. pub fn clear(&mut self) { self.count = 0; } } impl Default for ScatterGatherBuilder<'_, W> { fn default() -> Self { Self::new() } } /// A completed scatter-gather transfer result. /// /// This type is returned after a scatter-gather transfer completes, /// providing access to any error information. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ScatterGatherResult { /// Number of segments successfully transferred pub segments_completed: usize, /// Error if any occurred pub error: Option, } /// Interrupt handler helper. /// /// Call this from your interrupt handler to clear the interrupt flag and wake the waker. /// This handles both half-transfer and complete-transfer interrupts. /// /// # Safety /// Must be called from the correct DMA channel interrupt context. unsafe fn on_interrupt(ch_index: usize) { crate::perf_counters::incr_interrupt_edma0(); let edma = &pac::EDMA_0_TCD0; let t = edma.tcd(ch_index); // Read TCD CSR to determine interrupt source let csr = t.tcd_csr().read(); // Check if this is a half-transfer interrupt // INTHALF is set and we're at or past the half-way point if csr.inthalf() { let biter = t.tcd_biter_elinkno().read().biter(); let citer = t.tcd_citer_elinkno().read().citer(); let half_point = biter / 2; if citer <= half_point && citer > 0 { // Half-transfer interrupt - wake half_waker crate::perf_counters::incr_interrupt_edma0_wake(); half_waker(ch_index).wake(); } } // Clear INT flag t.ch_int().write(|w| w.set_int(true)); // If DONE is set, this is a complete-transfer interrupt // Only wake the full-transfer waker when the transfer is actually complete if t.ch_csr().read().done() { crate::perf_counters::incr_interrupt_edma0_wake(); waker(ch_index).wake(); } } /// Macro to generate DMA channel interrupt handlers. macro_rules! impl_dma_interrupt_handler { ($irq:ident, $ch:expr) => { #[interrupt] fn $irq() { // SAFETY: The correct $ch is called as generated, We check that // the given callback is non-null before calling. unsafe { on_interrupt($ch); // See https://doc.rust-lang.org/std/primitive.fn.html#casting-to-and-from-integers let cb: *mut () = CALLBACKS[$ch].load(Ordering::Acquire); if !cb.is_null() { let cb: fn() = core::mem::transmute(cb); (cb)(); } } } }; } use crate::pac::interrupt; impl_dma_interrupt_handler!(DMA_CH0, 0); impl_dma_interrupt_handler!(DMA_CH1, 1); impl_dma_interrupt_handler!(DMA_CH2, 2); impl_dma_interrupt_handler!(DMA_CH3, 3); impl_dma_interrupt_handler!(DMA_CH4, 4); impl_dma_interrupt_handler!(DMA_CH5, 5); impl_dma_interrupt_handler!(DMA_CH6, 6); impl_dma_interrupt_handler!(DMA_CH7, 7); // TODO(AJM): This is a gross, gross hack. This implements optional callbacks // for DMA completion interrupts. This should go away once we switch to // "in-band" DMA interrupt binding with `bind_interrupts!`. static CALLBACKS: [AtomicPtr<()>; 8] = [const { AtomicPtr::new(core::ptr::null_mut()) }; 8]; ================================================ FILE: embassy-mcxa/src/executor.rs ================================================ use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, Ordering}; use embassy_executor::{Spawner, raw}; use embassy_hal_internal::Peri; use crate::clocks::config::CoreSleep; use crate::gpio::{DriveStrength, GpioPin, Level, Output, SlewRate}; use crate::pac::gpio::vals::{Ptco, Ptso}; static TASKS_PENDING: AtomicBool = AtomicBool::new(false); static EXECUTOR_ONCE: AtomicU8 = AtomicU8::new(0); /// Information stored in format: /// /// ``` /// 0bAxxx_xxxx_xxxx_xxxx_OOOO_OOOO_IIII_IIII /// ``` /// /// Where: /// /// * A: 1 bit, "is active", so we can differentiate between "never set" /// and "use port 0 pin 0" /// * O: 8 bits, "pOrt number" /// * I: 8 bits, "pIn number" /// /// Initially set to `0`, which makes "set lo" and "set hi" a no-op static DEBUG_GPIO: AtomicU32 = AtomicU32::new(0); const EXECUTOR_UNINIT: u8 = 0; const EXECUTOR_TAKEN: u8 = 1; // Use a sentinel value for context to denote the thread pender context const THREAD_PENDER: usize = usize::MAX; pub struct Executor { inner: raw::Executor, not_send: PhantomData<*mut ()>, } /// TODO: Taken from embassy-stm32, verify this is necessary or what we want #[unsafe(export_name = "__pender")] fn __pender(context: *mut ()) { // 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; // Try to make Rust optimize the branching away if we only use thread mode. if context == THREAD_PENDER { TASKS_PENDING.store(true, Ordering::Release); cortex_m::asm::sev(); } } impl Executor { // Note: We don't really want a Default impl for this singleton. #[allow(clippy::new_without_default)] pub fn new() -> Self { let res = EXECUTOR_ONCE.compare_exchange(EXECUTOR_UNINIT, EXECUTOR_TAKEN, Ordering::AcqRel, Ordering::Relaxed); if res.is_err() { panic!("Can only take the executor once"); } 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)) -> ! { // We can only create the executor once init(self.inner.spawner()); // Until we've performed HAL init, just do WFE sleep let power_depth = loop { unsafe { self.inner.poll(); if go_around() { continue; } let sleep = crate::clocks::with_clocks(|c| c.core_sleep); if let Some(s) = sleep { break s; } debug_lo(); do_wfe(); debug_hi(); crate::perf_counters::incr_wfe_sleeps(); } }; match power_depth { // For Wfe sleep, our sleep target is constant. This means that // we don't need to do anything fancy here, just do a normal WFE // loop, since clock init already set our sleep mode parameters CoreSleep::WfeUngated | CoreSleep::WfeGated => loop { unsafe { self.inner.poll(); if go_around() { continue; } debug_lo(); do_wfe(); debug_hi(); crate::perf_counters::incr_wfe_sleeps(); } }, CoreSleep::DeepSleep => loop { unsafe { // For deep sleep, we need to be a bit more clever. First, poll any // pending tasks self.inner.poll(); if go_around() { continue; } // Next, we need to check if any high-power peripherals exist that should // inhibit us from entering deep sleep. Take a critical section to check. // // We STAY in the CS for the deep sleep to ensure that we handle wake-up // completely BEFORE yielding control flow back to interrupts. debug_lo(); let do_wfe_sleep = critical_section::with(|cs| { let did_deep_sleep = crate::clocks::deep_sleep_if_possible(&cs); if did_deep_sleep { debug_hi(); } !did_deep_sleep }); // Did we succeed at deep sleeping? if do_wfe_sleep { // Nope, WFE. We don't need a critical section here because we don't // need to wait for clocks to resume before we service interrupts. do_wfe(); debug_hi(); crate::perf_counters::incr_wfe_sleeps(); } else { // Yep! crate::perf_counters::incr_deep_sleeps(); } } }, } } } /// Every time we WFE, we want to do DSB; WFE. #[inline(always)] unsafe fn do_wfe() { cortex_m::asm::dsb(); cortex_m::asm::wfe(); } /// Function to go around to poll again, and clear a pending sev if we /// know we will immediately wake anyway. fn go_around() -> bool { let sev_pending = TASKS_PENDING.swap(false, Ordering::AcqRel); if sev_pending { // We know __pender has sent a sev, ack it with a wfe, which we // know will immediately return control flow to us. cortex_m::asm::wfe(); } sev_pending } /// Dedicate a pin to be used for introspecting the state of the custom executor. /// /// This pin will be driven low prior to entering WFE (light or deep sleep), /// and be raised once control flow is returned to the executor. /// /// For deep sleep, the pin will be raised *before* exiting the critical section, /// meaning that the level will go high before servicing any interrupts. For light /// sleep, no critical section is taken prior to deep sleep, so the pin will go /// high once the executor resumes, likely after processing any pending interrupts. /// /// This exact behavior is not considered semver stable, and may change at any time. /// /// This function consumes the given pin, overwriting any previous pin set as the executor /// debug pin. If this function is called multiple times, the previously assigned pin(s) /// will not be disabled, and will remain at their last updated state. pub fn set_executor_debug_gpio(pin: Peri<'static, impl GpioPin>) { let pin_num = pin.pin(); let port_num = pin.port(); // Setup GPIO as output, initially high let output = Output::new(pin, Level::High, DriveStrength::Normal, SlewRate::Slow); let number = 0x8000_0000 | ((port_num as u32) << 8) | (pin_num as u32); core::mem::forget(output); DEBUG_GPIO.store(number, Ordering::Relaxed); } /// Set low if we have a debug gpio set, using raw pac methods fn debug_lo() { let num = DEBUG_GPIO.load(Ordering::Relaxed); if num == 0 { return; } let port_num = (num >> 8) as u8; let pin_num = (num & 0xFF) as usize; match port_num { 0 => crate::pac::GPIO0.pcor(), 1 => crate::pac::GPIO1.pcor(), 2 => crate::pac::GPIO2.pcor(), 3 => crate::pac::GPIO3.pcor(), 4 => crate::pac::GPIO4.pcor(), _ => return, } .write(|w| w.set_ptco(pin_num, Ptco::PTCO1)); } /// Set high if we have a debug gpio set, using raw pac methods fn debug_hi() { let num = DEBUG_GPIO.load(Ordering::Relaxed); if num == 0 { return; } let port_num = (num >> 8) as u8; let pin_num = (num & 0xFF) as usize; match port_num { 0 => crate::pac::GPIO0.psor(), 1 => crate::pac::GPIO1.psor(), 2 => crate::pac::GPIO2.psor(), 3 => crate::pac::GPIO3.psor(), 4 => crate::pac::GPIO4.psor(), _ => return, } .write(|w| w.set_ptso(pin_num, Ptso::PTSO1)); } ================================================ FILE: embassy-mcxa/src/flash.rs ================================================ //! Flash driver for MCXA276 using ROM API. //! //! This module provides safe access to the MCXA276's internal flash memory through //! the ROM-resident flash driver API. The ROM API lives at a fixed address and exposes //! flash operations (init, erase, program, verify, read) via a function pointer table. //! //! # Flash Geometry (MCXA276) //! //! - Base address: `0x0000_0000` //! - Total size: 1 MB (`0x10_0000`) //! - Sector size: 8 KB (`0x2000`) — erase granularity //! - Page size: 128 bytes — program granularity //! - Phrase size: 16 bytes — minimum program unit //! //! # Safety //! //! All flash-modifying operations (erase, program) run inside a critical section //! to prevent interrupts from executing code in flash while it is being modified. //! After each modifying operation, speculation buffers and the LPCAC are cleared //! to ensure subsequent reads return fresh data. //! //! # Example //! //! ```rust,no_run //! #![no_std] //! #![no_main] //! //! # use panic_halt as _; //! use embassy_executor::Spawner; //! use embassy_mcxa::clocks::config::Div8; //! use embassy_mcxa::config::Config; //! use embassy_mcxa::flash::Flash; //! use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; //! //! #[embassy_executor::main] //! async fn main(_spawner: Spawner) { //! let mut config = Config::default(); //! config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); //! //! let p = embassy_mcxa::init(config); //! //! let mut flash = Flash::new().unwrap(); //! //! // Erase a sector at offset 0xFE000 (near the end of 1 MB flash) //! flash.erase(0xFE000, 0x10_0000).unwrap(); //! //! // Program 128 bytes at that offset (must be a multiple of 16-byte phrase size) //! let data = [0xABu8; 128]; //! flash.write(0xFE000, &data).unwrap(); //! //! // Read back //! let mut buf = [0u8; 128]; //! flash.read(0xFE000, &mut buf).unwrap(); //! } //! //! ``` use core::slice; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use crate::pac; use crate::pac::syscon::vals::{ClrLpcac, DisDataSpec, DisFlashSpec, DisLpcac, DisMbeccErrData, DisMbeccErrInst}; // --------------------------------------------------------------------------- // Flash geometry constants // --------------------------------------------------------------------------- /// Base address of the internal program flash. pub const FLASH_BASE: u32 = 0x0000_0000; /// Total size of the internal program flash in bytes (1 MB). pub const FLASH_SIZE: usize = 0x10_0000; /// Sector size in bytes (8 KB) — erase granularity. pub const SECTOR_SIZE: usize = 0x2000; /// Page size in bytes (128) — program-page granularity. pub const PAGE_SIZE: usize = 128; /// Phrase size in bytes (16) — minimum program unit. pub const PHRASE_SIZE: usize = 16; // --------------------------------------------------------------------------- // ROM API constants // --------------------------------------------------------------------------- /// Base address of the ROM API bootloader tree for MCXA276. const ROM_API_BASE: u32 = 0x0300_5FE0; /// Flash erase key: `FOUR_CHAR_CODE('l','f','e','k')` in little-endian. const FLASH_ERASE_KEY: u32 = 0x6B65_666C; // --------------------------------------------------------------------------- // ROM API C-ABI structures // --------------------------------------------------------------------------- /// Flash FFR (Factory Failure Records) configuration, populated by `flash_init`. #[repr(C)] #[derive(Debug, Default, Copy, Clone)] struct FlashFfrConfig { ffr_block_base: u32, ffr_total_size: u32, ffr_page_size: u32, sector_size: u32, cfpa_page_version: u32, cfpa_page_offset: u32, } /// Flash driver configuration, populated by `flash_init`. #[repr(C)] #[derive(Debug, Default, Copy, Clone)] struct FlashConfig { pflash_block_base: u32, pflash_total_size: u32, pflash_block_count: u32, pflash_page_size: u32, pflash_sector_size: u32, ffr_config: FlashFfrConfig, } // Type aliases for ROM API function pointer signatures (C ABI). // Only `flash_init` takes `*mut FlashConfig`; all other calls use `*const FlashConfig`. type FnFlashInit = unsafe extern "C" fn(config: *mut FlashConfig) -> i32; type FnFlashEraseSector = unsafe extern "C" fn(config: *const FlashConfig, start: u32, len: u32, key: u32) -> i32; type FnFlashProgramPhrase = unsafe extern "C" fn(config: *const FlashConfig, start: u32, src: *const u8, len: u32) -> i32; type FnFlashProgramPage = unsafe extern "C" fn(config: *const FlashConfig, start: u32, src: *const u8, len: u32) -> i32; type FnFlashVerifyProgram = unsafe extern "C" fn( config: *const FlashConfig, start: u32, len: u32, expected: *const u8, failed_addr: *mut u32, failed_data: *mut u32, ) -> i32; type FnFlashVerifyErasePhrase = unsafe extern "C" fn(config: *const FlashConfig, start: u32, len: u32) -> i32; type FnFlashVerifyErasePage = unsafe extern "C" fn(config: *const FlashConfig, start: u32, len: u32) -> i32; type FnFlashVerifyEraseSector = unsafe extern "C" fn(config: *const FlashConfig, start: u32, len: u32) -> i32; type FnFlashGetProperty = unsafe extern "C" fn(config: *const FlashConfig, property: u32, value: *mut u32) -> i32; type FnFlashRead = unsafe extern "C" fn(config: *const FlashConfig, start: u32, dest: *mut u8, len: u32) -> i32; /// ROM API flash driver interface vtable. /// /// **Layout note**: On MCXA276, `FSL_FEATURE_ROMAPI_IFR == 0`, so the three /// IFR function pointers are *omitted*. `flash_read` and `version` follow /// immediately after `flash_get_property`. #[repr(C)] struct FlashDriverInterface { flash_init: FnFlashInit, flash_erase_sector: FnFlashEraseSector, flash_program_phrase: FnFlashProgramPhrase, flash_program_page: FnFlashProgramPage, flash_verify_program: FnFlashVerifyProgram, flash_verify_erase_phrase: FnFlashVerifyErasePhrase, flash_verify_erase_page: FnFlashVerifyErasePage, flash_verify_erase_sector: FnFlashVerifyEraseSector, flash_get_property: FnFlashGetProperty, // IFR functions omitted (FSL_FEATURE_ROMAPI_IFR == 0 on MCXA276) flash_read: FnFlashRead, version: u32, } /// Root of the ROM bootloader API tree. #[repr(C)] struct BootloaderTree { run_bootloader: unsafe extern "C" fn(arg: *mut core::ffi::c_void), flash_driver: *const FlashDriverInterface, jump: unsafe extern "C" fn(arg: *mut core::ffi::c_void), } /// Returns a reference to the ROM API flash driver interface. #[inline(always)] fn flash_api() -> &'static FlashDriverInterface { unsafe { let tree = &*(ROM_API_BASE as *const BootloaderTree); &*tree.flash_driver } } // --------------------------------------------------------------------------- // Error types // --------------------------------------------------------------------------- /// Errors that can occur during flash operations. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { /// Address or range is outside the flash region. OutOfBounds, /// Address or length is not properly aligned. Unaligned, /// The ROM API returned a non-zero status code. RomApi(i32), } /// Flash property identifiers (ROM API `flash_get_property`). #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum FlashProperty { /// Pflash sector size property. PflashSectorSize = 0x00, /// Pflash total size property. PflashTotalSize = 0x01, /// Pflash block size property. PflashBlockSize = 0x02, /// Pflash block count property. PflashBlockCount = 0x03, /// Pflash block base address property. PflashBlockBaseAddr = 0x04, /// Pflash page size property. PflashPageSize = 0x30, /// Pflash system frequency property. PflashSystemFreq = 0x31, /// FFR sector size property. FfrSectorSize = 0x40, /// FFR total size property. FfrTotalSize = 0x41, /// FFR block base address property. FfrBlockBaseAddr = 0x42, /// FFR page size property. FfrPageSize = 0x43, } impl NorFlashError for Error { fn kind(&self) -> NorFlashErrorKind { match self { Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, Self::Unaligned => NorFlashErrorKind::NotAligned, Self::RomApi(_) => NorFlashErrorKind::Other, } } } /// Convert a ROM API status code to a `Result`. #[inline] fn check_status(status: i32) -> Result<(), Error> { if status == 0 { Ok(()) } else { Err(Error::RomApi(status)) } } // --------------------------------------------------------------------------- // Cache clearing helpers (must be called after erase/program) // --------------------------------------------------------------------------- /// Clear flash and data speculation buffers by toggling the disable bits /// in SYSCON->NVM_CTRL, matching the C `speculation_buffer_clear()`. #[inline] fn speculation_buffer_clear() { let nvm = pac::SYSCON.nvm_ctrl(); let val = nvm.read(); // Only proceed if MBECC error reporting is enabled for both inst and data if val.dis_mbecc_err_inst() == DisMbeccErrInst::ENABLE && val.dis_mbecc_err_data() == DisMbeccErrData::ENABLE { // Toggle flash speculation disable if val.dis_flash_spec() == DisFlashSpec::ENABLE { nvm.modify(|w| w.set_dis_flash_spec(DisFlashSpec::DISABLE)); nvm.modify(|w| w.set_dis_flash_spec(DisFlashSpec::ENABLE)); } // Toggle data speculation disable if nvm.read().dis_data_spec() == DisDataSpec::ENABLE { nvm.modify(|w| w.set_dis_data_spec(DisDataSpec::DISABLE)); nvm.modify(|w| w.set_dis_data_spec(DisDataSpec::ENABLE)); } } } /// Clear LPCAC by setting the CLR_LPCAC bit in SYSCON->LPCAC_CTRL, /// matching the C `lpcac_clear()`. #[inline] fn lpcac_clear() { let lpcac = pac::SYSCON.lpcac_ctrl(); if lpcac.read().dis_lpcac() == DisLpcac::ENABLE { lpcac.modify(|w| w.set_clr_lpcac(ClrLpcac::DISABLE)); } } /// Combined cache clearing: speculation buffers + LPCAC. #[inline] fn clear_caches() { speculation_buffer_clear(); lpcac_clear(); } // --------------------------------------------------------------------------- // Flash driver // --------------------------------------------------------------------------- /// Flash driver providing safe access to the MCXA276 internal flash via ROM API. /// /// The driver holds an internal `FlashConfig` that is initialised by the ROM /// API's `flash_init` function during construction. All subsequent operations /// pass this config to the ROM API. pub struct Flash { config: FlashConfig, } impl Flash { /// Create and initialise a new flash driver. /// /// This calls the ROM API `flash_init` to populate the internal flash /// configuration. Returns an error if the ROM API reports failure. pub fn new() -> Result { let mut config = FlashConfig::default(); let status = unsafe { (flash_api().flash_init)(&mut config) }; check_status(status)?; Ok(Self { config }) } /// Erase flash sectors encompassing the given absolute address range. /// /// - `address`: absolute start address (must be sector-aligned). /// - `len`: number of bytes to erase (must be a multiple of sector size). /// /// Runs inside a critical section and clears caches afterwards. pub fn blocking_erase(&mut self, address: u32, len: u32) -> Result<(), Error> { let status = cortex_m::interrupt::free(|_| unsafe { (flash_api().flash_erase_sector)(&self.config, address, len, FLASH_ERASE_KEY) }); clear_caches(); check_status(status) } /// Program a phrase (16 bytes) of data at the given absolute address. /// /// - `address`: absolute start address (must be phrase-aligned). /// - `data`: source buffer whose length must be a multiple of `PHRASE_SIZE`. /// /// Runs inside a critical section and clears caches afterwards. pub fn blocking_program_phrase(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { let status = cortex_m::interrupt::free(|_| unsafe { (flash_api().flash_program_phrase)(&self.config, address, data.as_ptr(), data.len() as u32) }); clear_caches(); check_status(status) } /// Program a page of data at the given absolute address. /// /// - `address`: absolute start address (must be page-aligned). /// - `data`: source buffer whose length must be a multiple of `PAGE_SIZE`. /// /// Runs inside a critical section and clears caches afterwards. pub fn blocking_program(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { let status = cortex_m::interrupt::free(|_| unsafe { (flash_api().flash_program_page)(&self.config, address, data.as_ptr(), data.len() as u32) }); clear_caches(); check_status(status) } /// Verify that the programmed data at `address` matches `expected`. /// /// On mismatch, returns `Error::RomApi` with the ROM status code. pub fn verify_program(&mut self, address: u32, expected: &[u8]) -> Result<(), Error> { let mut failed_address: u32 = 0; let mut failed_data: u32 = 0; let status = unsafe { (flash_api().flash_verify_program)( &self.config, address, expected.len() as u32, expected.as_ptr(), &mut failed_address, &mut failed_data, ) }; check_status(status) } /// Verify that the sector(s) starting at `address` are erased. pub fn verify_erase_sector(&mut self, address: u32, len: u32) -> Result<(), Error> { let status = unsafe { (flash_api().flash_verify_erase_sector)(&self.config, address, len) }; check_status(status) } /// Read flash data using the ROM API. /// /// - `address`: absolute start address. /// - `dest`: destination buffer. pub fn blocking_read_rom(&mut self, address: u32, dest: &mut [u8]) -> Result<(), Error> { let status = unsafe { (flash_api().flash_read)(&self.config, address, dest.as_mut_ptr(), dest.len() as u32) }; check_status(status) } /// Read flash data by direct memory-mapped access (no ROM API call). /// /// - `offset`: byte offset from flash base (`0x0000_0000`). /// - `dest`: destination buffer. pub fn blocking_read(&self, offset: u32, dest: &mut [u8]) -> Result<(), Error> { if offset as usize + dest.len() > FLASH_SIZE { return Err(Error::OutOfBounds); } let src = unsafe { slice::from_raw_parts((FLASH_BASE + offset) as *const u8, dest.len()) }; dest.copy_from_slice(src); Ok(()) } /// Return the ROM API version. pub fn rom_api_version(&self) -> u32 { flash_api().version } /// Get a ROM API flash property value. pub fn get_property(&mut self, property: FlashProperty) -> Result { let mut value: u32 = 0; let status = unsafe { (flash_api().flash_get_property)(&self.config, property as u32, &mut value) }; check_status(status)?; Ok(value) } } // --------------------------------------------------------------------------- // embedded-storage trait implementations // --------------------------------------------------------------------------- impl ErrorType for Flash { type Error = Error; } impl ReadNorFlash for Flash { const READ_SIZE: usize = 1; fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { self.blocking_read(offset, bytes) } fn capacity(&self) -> usize { FLASH_SIZE } } impl NorFlash for Flash { const WRITE_SIZE: usize = PHRASE_SIZE; const ERASE_SIZE: usize = SECTOR_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).is_multiple_of(Self::ERASE_SIZE) || !(to as usize).is_multiple_of(Self::ERASE_SIZE) { return Err(Error::Unaligned); } // Erase one sector at a time for sector_addr in (from..to).step_by(Self::ERASE_SIZE) { self.blocking_erase(FLASH_BASE + sector_addr, SECTOR_SIZE as u32)?; } 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).is_multiple_of(Self::WRITE_SIZE) || !bytes.len().is_multiple_of(Self::WRITE_SIZE) { return Err(Error::Unaligned); } // Program one phrase at a time (16 bytes — the smallest write unit) for (i, chunk) in bytes.chunks(PHRASE_SIZE).enumerate() { let addr = FLASH_BASE + offset + (i * PHRASE_SIZE) as u32; self.blocking_program_phrase(addr, chunk)?; } Ok(()) } } ================================================ FILE: embassy-mcxa/src/gpio.rs ================================================ //! GPIO driver built around a type-erased `Flex` pin, similar to other Embassy HALs. //! The exported `Output`/`Input` drivers own a `Flex` so they no longer depend on the //! concrete pin type. use core::convert::Infallible; use core::future::Future; use core::marker::PhantomData; use core::pin::pin; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitMap; use paste::paste; use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac::common::{RW, Reg}; use crate::pac::gpio::vals::{Irqc, Isf, Pdd, Pid, Ptco, Ptso}; use crate::pac::port::regs::Pcr; use crate::pac::port::vals::{Dse, Ibe, Inv, Mux, Ode, Pe, Ps, Sre}; struct BitIter(u32); impl Iterator for BitIter { type Item = usize; fn next(&mut self) -> Option { match self.0.trailing_zeros() { 32 => None, b => { self.0 &= !(1 << b); Some(b as usize) } } } } #[cfg(feature = "mcxa2xx")] const PORT_COUNT: usize = 5; #[cfg(feature = "mcxa5xx")] const PORT_COUNT: usize = 6; static PORT_WAIT_MAPS: [WaitMap; PORT_COUNT] = [const { WaitMap::new() }; PORT_COUNT]; #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType { type Interrupt: Interrupt; } struct Info { pub port_index: usize, pub gpio: crate::pac::gpio::Gpio, } pub trait PeriGpioExt<'d, T: HasGpioInstance> { /// Type erase the pin while also binding and Irq. /// /// This means the [`AnyPin`] can be used to constuct an async [`Input`] with [`Flex::async_from_anypin`] /// and an async [`Flex`] with [`Flex::async_from_anypin`]. fn degrade_async( self, _irq: impl crate::interrupt::typelevel::Binding<::Interrupt, InterruptHandler>, ) -> Peri<'d, AnyPin>; } impl<'d, T: HasGpioInstance> PeriGpioExt<'d, T> for Peri<'d, T> { /// Type erase the pin while also binding and Irq. /// /// This means the [`AnyPin`] can be used to constuct an async [`Input`] with [`Flex::async_from_anypin`] /// and an async [`Flex`] with [`Flex::async_from_anypin`]. fn degrade_async( self, _irq: impl crate::interrupt::typelevel::Binding<::Interrupt, InterruptHandler>, ) -> Peri<'d, AnyPin> { HasGpioInstance::degrade_async(self, _irq) } } pub trait HasGpioInstance: GpioPin { type Instance: Instance; /// Type erase the pin while also binding and Irq. /// /// This means the [`AnyPin`] can be used to constuct an async [`Input`] with [`Flex::async_from_anypin`] /// and an async [`Flex`] with [`Flex::async_from_anypin`]. fn degrade_async<'p>( this: Peri<'p, Self>, _irq: impl crate::interrupt::typelevel::Binding< ::Interrupt, InterruptHandler, >, ) -> Peri<'p, AnyPin>; } trait SealedInstance { fn info() -> &'static Info; const PERF_INT_INCR: fn(); } macro_rules! impl_instance { ($($n:expr),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { gpio: crate::pac::[], port_index: $n, }; &INFO } const PERF_INT_INCR: fn() = crate::perf_counters::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0, 1, 2, 3, 4); #[cfg(feature = "mcxa5xx")] impl_instance!(5); pub struct InterruptHandler { _phantom: PhantomData, } impl Handler for InterruptHandler { unsafe fn on_interrupt() { let info = T::info(); let isfr = info.gpio.isfr(0); for pin in BitIter(isfr.read().0) { // Clear all pending interrupts isfr.write(|w| w.0 = 1 << pin); info.gpio.icr(pin).modify(|w| w.set_irqc(Irqc::IRQC0)); // Disable interrupt // Wake the corresponding port waker if let Some(w) = PORT_WAIT_MAPS.get(info.port_index) { T::PERF_INT_INCR(); w.wake(&pin, ()); } } } } /// Open-drain for GPIO pins. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OpenDrain { /// Output is push-pull (not open-drain) No, /// Output is open-drain Yes, } impl From for Ode { fn from(open_drain: OpenDrain) -> Self { match open_drain { OpenDrain::No => Ode::ODE0, OpenDrain::Yes => Ode::ODE1, } } } /// Logical level for GPIO pins. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Level { Low, High, } impl From for Level { fn from(val: bool) -> Self { match val { true => Self::High, false => Self::Low, } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Pull { Disabled, Up, Down, } impl From for (Pe, Ps) { fn from(pull: Pull) -> Self { match pull { Pull::Disabled => (Pe::PE0, Ps::PS0), Pull::Up => (Pe::PE1, Ps::PS1), Pull::Down => (Pe::PE1, Ps::PS0), } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum SlewRate { Fast, Slow, } impl From for Sre { fn from(slew_rate: SlewRate) -> Self { match slew_rate { SlewRate::Fast => Sre::SRE0, SlewRate::Slow => Sre::SRE1, } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum DriveStrength { Normal, Double, } impl From for Dse { fn from(strength: DriveStrength) -> Self { match strength { DriveStrength::Normal => Dse::DSE0, DriveStrength::Double => Dse::DSE1, } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Inverter { Disabled, Enabled, } impl From for Inv { fn from(strength: Inverter) -> Self { match strength { Inverter::Disabled => Inv::INV0, Inverter::Enabled => Inv::INV1, } } } pub type Gpio = crate::peripherals::GPIO0; /// Type-erased representation of a GPIO pin. pub struct AnyPin { port: u8, pin: u8, gpio: crate::pac::gpio::Gpio, port_reg: crate::pac::port::Port, pcr_reg: Reg, irq_bound: bool, } impl AnyPin { /// Create an `AnyPin` from raw components. pub(crate) fn new( port: u8, pin: u8, gpio: crate::pac::gpio::Gpio, port_reg: crate::pac::port::Port, pcr_reg: Reg, irq_bound: bool, ) -> Self { Self { port, pin, gpio, port_reg, pcr_reg, irq_bound, } } #[inline(always)] fn gpio(&self) -> crate::pac::gpio::Gpio { self.gpio } #[inline(always)] pub fn port_index(&self) -> u8 { self.port } #[inline(always)] pub fn pin_index(&self) -> u8 { self.pin } #[inline(always)] fn port_reg(&self) -> crate::pac::port::Port { self.port_reg } #[inline(always)] fn pcr_reg(&self) -> Reg { self.pcr_reg } #[inline(always)] fn irq_bound(&self) -> bool { self.irq_bound } } embassy_hal_internal::impl_peripheral!(AnyPin); pub(crate) trait SealedPin { fn port(&self) -> u8; fn pin(&self) -> u8; fn gpio(&self) -> crate::pac::gpio::Gpio; fn port_reg(&self) -> crate::pac::port::Port; fn pcr_reg(&self) -> Reg; fn set_function(&self, function: Mux); fn set_pull(&self, pull: Pull); fn set_drive_strength(&self, strength: Dse); fn set_slew_rate(&self, slew_rate: Sre); fn set_enable_input_buffer(&self, buffer_enabled: bool); fn set_as_disabled(&self); } /// GPIO pin trait. #[allow(private_bounds)] pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { /// Type-erase the pin. fn degrade(self) -> AnyPin { // SAFETY: This is only called within the GpioPin trait, which is only // implemented within this module on valid pin peripherals and thus // has been verified to be correct. AnyPin::new( self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg(), false, ) } } impl SealedPin for AnyPin { #[inline(always)] fn pin(&self) -> u8 { self.pin_index() } #[inline(always)] fn port(&self) -> u8 { self.port_index() } #[inline(always)] fn gpio(&self) -> crate::pac::gpio::Gpio { self.gpio() } #[inline(always)] fn port_reg(&self) -> crate::pac::port::Port { self.port_reg() } #[inline(always)] fn pcr_reg(&self) -> Reg { self.pcr_reg() } #[inline(always)] fn set_function(&self, function: Mux) { self.pcr_reg().modify(|w| w.set_mux(function)); } #[inline(always)] fn set_pull(&self, pull: Pull) { let (pull_enable, pull_select) = pull.into(); self.pcr_reg().modify(|w| { w.set_pe(pull_enable); w.set_ps(pull_select) }); } #[inline(always)] fn set_drive_strength(&self, strength: Dse) { self.pcr_reg().modify(|w| w.set_dse(strength)); } #[inline(always)] fn set_slew_rate(&self, slew_rate: Sre) { self.pcr_reg().modify(|w| w.set_sre(slew_rate)); } #[inline(always)] fn set_enable_input_buffer(&self, buffer_enabled: bool) { self.pcr_reg() .modify(|w| w.set_ibe(if buffer_enabled { Ibe::IBE1 } else { Ibe::IBE0 })); } #[inline(always)] fn set_as_disabled(&self) { // Set GPIO direction as input self.gpio().pddr().modify(|w| w.set_pdd(self.pin() as usize, Pdd::PDD0)); // Set input buffer as disabled self.set_enable_input_buffer(false); // Set mode as GPIO (vs other potential functions) self.set_function(Mux::MUX0); // Set pin as disabled self.gpio().pidr().modify(|w| w.set_pid(self.pin() as usize, Pid::PID1)); } } impl GpioPin for AnyPin {} #[doc(hidden)] #[macro_export] macro_rules! impl_pin { ($peri:ident, $port:expr, $pin:expr, $block:ident) => { ::paste::paste! { impl SealedPin for $crate::peripherals::$peri { #[inline(always)] fn port(&self) -> u8 { $port } #[inline(always)] fn pin(&self) -> u8 { $pin } #[inline(always)] fn gpio(&self) -> crate::pac::gpio::Gpio { crate::pac::$block } #[inline(always)] fn port_reg(&self) -> crate::pac::port::Port { crate::pac::[] } #[inline(always)] fn pcr_reg(&self) -> Reg { self.port_reg().pcr($pin) } #[inline(always)] fn set_function(&self, function: Mux) { self.pcr_reg().modify(|w| w.set_mux(function)); } #[inline(always)] fn set_pull(&self, pull: Pull) { let (pull_enable, pull_select) = pull.into(); self.pcr_reg().modify(|w| { w.set_pe(pull_enable); w.set_ps(pull_select); }); } #[inline(always)] fn set_drive_strength(&self, strength: Dse) { self.pcr_reg().modify(|w| w.set_dse(strength)); } #[inline(always)] fn set_slew_rate(&self, slew_rate: Sre) { self.pcr_reg().modify(|w| w.set_sre(slew_rate)); } #[inline(always)] fn set_enable_input_buffer(&self, buffer_enabled: bool) { self.pcr_reg().modify(|w| w.set_ibe(if buffer_enabled { Ibe::IBE1 } else { Ibe::IBE0 })); } #[inline(always)] fn set_as_disabled(&self) { // Set GPIO direction as input self.gpio().pddr().modify(|w| w.set_pdd(self.pin() as usize, Pdd::PDD0)); // Set input buffer as disabled self.set_enable_input_buffer(false); // Set mode as GPIO (vs other potential functions) self.set_function(Mux::MUX0); // Set pin as disabled self.gpio().pidr().modify(|w| w.set_pid(self.pin() as usize, Pid::PID1)); } } impl GpioPin for crate::peripherals::$peri {} impl From for AnyPin { fn from(value: crate::peripherals::$peri) -> Self { value.degrade() } } impl crate::peripherals::$peri { /// Convenience helper to obtain a type-erased handle to this pin. pub fn degrade(&self) -> AnyPin { AnyPin::new( self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg(), false, ) } } impl crate::gpio::HasGpioInstance for crate::peripherals::$peri { type Instance = crate::peripherals::$block; fn degrade_async<'p>( this: embassy_hal_internal::Peri<'p, Self>, _irq: impl crate::interrupt::typelevel::Binding< ::Interrupt, crate::gpio::InterruptHandler, >, ) -> embassy_hal_internal::Peri<'p, AnyPin> { use crate::interrupt::typelevel::Interrupt; unsafe { <::Instance as crate::gpio::Instance>::Interrupt::enable(); } unsafe { embassy_hal_internal::Peri::new_unchecked(AnyPin::new( this.port(), this.pin(), this.gpio(), this.port_reg(), this.pcr_reg(), true, )) } } } } }; } mod sealed { pub trait Sealed {} } pub trait Mode: sealed::Sealed {} pub struct Async {} impl sealed::Sealed for Async {} impl Mode for Async {} pub struct Blocking {} impl sealed::Sealed for Blocking {} impl Mode for Blocking {} /// A flexible pin that can be configured as input or output. pub struct Flex<'d, M: Mode = Blocking> { pin: Peri<'d, AnyPin>, _phantom: PhantomData<&'d mut M>, } impl<'d> Flex<'d> { /// Wrap the pin in a `Flex`. /// /// The pin remains unmodified. The initial output level is unspecified, but /// can be changed before the pin is put into output mode. pub fn new(pin: Peri<'d, impl GpioPin>) -> Self { pin.set_function(Mux::MUX0); Self { pin: pin.into(), _phantom: PhantomData, } } } impl<'d, M: Mode> Flex<'d, M> { #[inline] fn gpio(&self) -> crate::pac::gpio::Gpio { self.pin.gpio() } /// Put the pin into input mode. pub fn set_as_input(&mut self) { self.set_enable_input_buffer(true); self.gpio() .pddr() .modify(|w| w.set_pdd(self.pin.pin_index() as usize, Pdd::PDD0)); } /// Put the pin into output mode. pub fn set_as_output(&mut self) { self.set_pull(Pull::Disabled); self.gpio() .pddr() .modify(|w| w.set_pdd(self.pin.pin_index() as usize, Pdd::PDD1)); } /// Set output level to High. #[inline] pub fn set_high(&mut self) { self.gpio() .psor() .write(|w| w.set_ptso(self.pin.pin_index() as usize, Ptso::PTSO1)); } /// Set output level to Low. #[inline] pub fn set_low(&mut self) { self.gpio() .pcor() .write(|w| w.set_ptco(self.pin.pin_index() as usize, Ptco::PTCO1)); } /// Set output level to the given `Level`. #[inline] pub fn set_level(&mut self, level: Level) { match level { Level::High => self.set_high(), Level::Low => self.set_low(), } } /// Toggle output level. #[inline] pub fn toggle(&mut self) { self.gpio() .ptor() .write(|w| w.set_ptto(self.pin.pin_index() as usize, true)); } /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { self.gpio().pdir().read().pdi(self.pin.pin_index() as usize) } /// Get whether the pin input level is low. #[inline] pub fn is_low(&self) -> bool { !self.is_high() } /// Is the output pin set as high? #[inline] pub fn is_set_high(&self) -> bool { #[cfg(feature = "mcxa2xx")] let set = self.gpio().pdor().read().pdo(self.pin.pin_index() as usize); #[cfg(feature = "mcxa5xx")] let set = (self.gpio().pdor().read().0 & (1 << self.pin.pin_index())) != 0; set } /// Is the output pin set as low? #[inline] pub fn is_set_low(&self) -> bool { !self.is_set_high() } /// Configure open-drain output. #[inline] pub fn set_open_drain(&mut self, open_drain: OpenDrain) { self.pin.pcr_reg().modify(|w| w.set_ode(open_drain.into())); } /// Configure the input logic inversion of this pin. #[inline] pub fn set_input_inversion(&mut self, invert: Inverter) { self.pin.pcr_reg().modify(|w| w.set_inv(invert.into())); } /// Configure the pin pull up/down level. pub fn set_pull(&mut self, pull_select: Pull) { self.pin.set_pull(pull_select); } /// Configure the pin drive strength. pub fn set_drive_strength(&mut self, strength: DriveStrength) { self.pin.set_drive_strength(strength.into()); } /// Configure the pin slew rate. pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { self.pin.set_slew_rate(slew_rate.into()); } /// Enable input buffer for the pin. pub fn set_enable_input_buffer(&mut self, buffer_enabled: bool) { self.pin.set_enable_input_buffer(buffer_enabled); } /// Get pin level. pub fn get_level(&self) -> Level { self.is_high().into() } } #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct NoIrqBound; /// Async methods impl<'d> Flex<'d, Async> { /// Wrap the pin in Flex with Async support. /// /// This enables the use of async functions like: [`Flex::wait_for_high`] and [`Flex::wait_for_falling_edge`]. pub fn new_async

( pin: Peri<'d, P>, _irq: impl crate::interrupt::typelevel::Binding<::Interrupt, InterruptHandler>, ) -> Self where P: GpioPin + HasGpioInstance, { pin.set_function(Mux::MUX0); unsafe { ::Interrupt::enable(); } Self { pin: pin.into(), _phantom: PhantomData, } } /// Wrap an [`AnyPin`] in Flex with Async support. /// /// This enables the use of async functions like: [`Input::wait_for_high`] and [`Input::wait_for_falling_edge`]. /// In order to use an [`AnyPin`] with this function it needs to be constructed by /// calling [`PeriGpioExt::degrade_async`] on the pin to bind the Irq. /// If an [`AnyPin`] is provided that was not constucted with [`PeriGpioExt::degrade_async`], /// it will return the error: [`NoIrqBound`]. pub fn async_from_anypin(pin: Peri<'d, AnyPin>) -> Result { pin.set_function(Mux::MUX0); if pin.irq_bound() { Ok(Self { pin: pin.into(), _phantom: PhantomData, }) } else { Err(NoIrqBound) } } /// Helper function that waits for a given interrupt trigger async fn wait_for_inner(&mut self, level: crate::pac::gpio::vals::Irqc) { // First, ensure that we have a waker that is ready for this port+pin let w = PORT_WAIT_MAPS[usize::from(self.pin.port)].wait(self.pin.pin.into()); let mut w = pin!(w); // Wait for the subscription to occur, which requires polling at least once // // This function returns a result, but can only be an Err if: // // * We call `.close()` on a WaitMap, which we never do // * We have a duplicate key, which can't happen because `wait_for_*` methods // take an &mut ref of their unique port+pin combo // // So we wait for it to complete, but ignore the result. _ = w.as_mut().subscribe().await; // Now that our waker is in the map, we can enable the appropriate interrupt // // Clear any existing pending interrupt on this pin self.pin.gpio().isfr(0).write(|w| w.0 = 1 << self.pin.pin()); self.pin .gpio() .icr(self.pin.pin().into()) .write(|w| w.set_isf(Isf::ISF1)); // Pin interrupt configuration self.pin.gpio().icr(self.pin.pin().into()).modify(|w| w.set_irqc(level)); // Finally, we can await the matching call to `.wake()` from the interrupt. // // Again, technically, this could return a result, but for the same reasons // as above, this can't be an error in our case, so just wait for it to complete _ = w.await; } /// Wait until the pin is high. If it is already high, return immediately. #[inline] pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { self.wait_for_inner(Irqc::IRQC12) } /// Wait until the pin is low. If it is already low, return immediately. #[inline] pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { self.wait_for_inner(Irqc::IRQC8) } /// Wait for the pin to undergo a transition from low to high. #[inline] pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { self.wait_for_inner(Irqc::IRQC9) } /// Wait for the pin to undergo a transition from high to low. #[inline] pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { self.wait_for_inner(Irqc::IRQC10) } /// Wait for the pin to undergo any transition, i.e low to high OR high to low. #[inline] pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { self.wait_for_inner(Irqc::IRQC11) } } impl<'d, M: Mode> Drop for Flex<'d, M> { #[inline] fn drop(&mut self) { self.pin.set_as_disabled(); } } /// GPIO output driver that owns a `Flex` pin. pub struct Output<'d> { flex: Flex<'d>, } impl<'d> Output<'d> { /// Create a GPIO output driver for a [GpioPin] with the provided [Level]. pub fn new(pin: Peri<'d, impl GpioPin>, initial: Level, strength: DriveStrength, slew_rate: SlewRate) -> Self { let mut flex = Flex::new(pin); flex.set_level(initial); flex.set_as_output(); flex.set_drive_strength(strength); flex.set_slew_rate(slew_rate); flex.set_open_drain(OpenDrain::No); Self { flex } } /// Set the output as high. #[inline] pub fn set_high(&mut self) { self.flex.set_high(); } /// Set the output as low. #[inline] pub fn set_low(&mut self) { self.flex.set_low(); } /// Set the output level. #[inline] pub fn set_level(&mut self, level: Level) { self.flex.set_level(level); } /// Toggle the output level. #[inline] pub fn toggle(&mut self) { self.flex.toggle(); } /// Is the output pin set as high? #[inline] pub fn is_set_high(&self) -> bool { self.flex.is_set_high() } /// Is the output pin set as low? #[inline] pub fn is_set_low(&self) -> bool { self.flex.is_set_low() } /// Expose the inner `Flex` if callers need to reconfigure the pin. #[inline] pub fn into_flex(self) -> Flex<'d> { self.flex } /// Convert this output pin into an open-drain output pin. #[inline] pub fn into_open_drain(mut self) -> OutputOpenDrain<'d> { self.flex.set_open_drain(OpenDrain::Yes); OutputOpenDrain { flex: self.flex } } } /// GPIO output open-drain driver that owns a `Flex` pin. pub struct OutputOpenDrain<'d> { flex: Flex<'d>, } impl<'d> OutputOpenDrain<'d> { /// Create a GPIO output open-drain driver for a [GpioPin] with the provided [Level]. pub fn new(pin: Peri<'d, impl GpioPin>, initial: Level, strength: DriveStrength, slew_rate: SlewRate) -> Self { let mut flex = Flex::new(pin); flex.set_level(initial); flex.set_as_output(); flex.set_drive_strength(strength); flex.set_slew_rate(slew_rate); flex.set_enable_input_buffer(true); flex.set_open_drain(OpenDrain::Yes); Self { flex } } /// Get whether the pin level is high. #[inline] pub fn is_high(&self) -> bool { self.flex.is_high() } /// Get whether the pin level is low. #[inline] pub fn is_low(&self) -> bool { self.flex.is_low() } /// Set the output as high (open-drain high is just letting go of the line). #[inline] pub fn set_high(&mut self) { self.flex.set_high(); } /// Set the output as low (open-drain low is driving the line low). #[inline] pub fn set_low(&mut self) { self.flex.set_low(); } /// Set the output level. #[inline] pub fn set_level(&mut self, level: Level) { self.flex.set_level(level); } /// Get the pin level. pub fn get_level(&self) -> Level { self.flex.get_level() } /// Toggle the output level. #[inline] pub fn toggle(&mut self) { if self.flex.is_set_low() { self.set_high(); } else { self.set_low(); } } /// Configure the input logic inversion of this pin. #[inline] pub fn set_inversion(&mut self, invert: Inverter) { self.flex.set_input_inversion(invert) } /// Expose the inner `Flex` if callers need to reconfigure the pin. #[inline] pub fn into_flex(self) -> Flex<'d> { self.flex } /// Convert this output pin into an push-pull output pin. #[inline] pub fn into_push_pull(mut self) -> Output<'d> { self.flex.set_open_drain(OpenDrain::No); Output { flex: self.flex } } } /// GPIO input driver that owns a `Flex` pin. pub struct Input<'d, M: Mode = Blocking> { flex: Flex<'d, M>, } impl<'d> Input<'d> { /// Create a GPIO input driver for a [GpioPin]. /// pub fn new(pin: Peri<'d, impl GpioPin>, pull_select: Pull) -> Self { let mut flex = Flex::new(pin); flex.set_as_input(); flex.set_pull(pull_select); Self { flex } } } impl<'d, M: Mode> Input<'d, M> { /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { self.flex.is_high() } /// Get whether the pin input level is low. #[inline] pub fn is_low(&self) -> bool { self.flex.is_low() } /// Expose the inner `Flex` if callers need to reconfigure the pin. /// /// Since Drive Strength and Slew Rate are not set when creating the Input /// pin, they need to be set when converting #[inline] pub fn into_flex(mut self, strength: DriveStrength, slew_rate: SlewRate) -> Flex<'d, M> { self.flex.set_drive_strength(strength); self.flex.set_slew_rate(slew_rate); self.flex } /// Configure the input logic inversion of this pin. #[inline] pub fn set_inversion(&mut self, invert: Inverter) { self.flex.set_input_inversion(invert) } /// Get the pin level. pub fn get_level(&self) -> Level { self.flex.get_level() } } /// Async methods impl<'d> Input<'d, Async> { /// Create a GPIO input driver for a [GpioPin] with async support. /// /// This enables the use of async functions like: [`Input::wait_for_high`] and [`Input::wait_for_falling_edge`]. pub fn new_async

( pin: Peri<'d, P>, irq: impl crate::interrupt::typelevel::Binding<::Interrupt, InterruptHandler> + 'd, pull_select: Pull, ) -> Self where P: GpioPin + HasGpioInstance, { let mut flex = Flex::new_async(pin, irq); flex.set_as_input(); flex.set_pull(pull_select); Self { flex } } /// Create a GPIO input driver for a [GpioPin] with async support from an [`AnyPin`]. /// /// This enables the use of async functions like: [`Input::wait_for_high`] and [`Input::wait_for_falling_edge`]. /// In order to use an [`AnyPin`] with this function it needs to be constructed by /// calling [`PeriGpioExt::degrade_async`] on the pin to bind the Irq. /// If an [`AnyPin`] is provided that was not constucted with [`PeriGpioExt::degrade_async`], /// it will return the error: [`NoIrqBound`]. pub fn async_from_anypin(pin: Peri<'d, AnyPin>, pull_select: Pull) -> Result { let mut flex = Flex::async_from_anypin(pin)?; flex.set_as_input(); flex.set_pull(pull_select); Ok(Self { flex }) } /// Wait until the pin is high. If it is already high, return immediately. #[inline] pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { self.flex.wait_for_high() } /// Wait until the pin is low. If it is already low, return immediately. #[inline] pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { self.flex.wait_for_low() } /// Wait for the pin to undergo a transition from low to high. #[inline] pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { self.flex.wait_for_rising_edge() } /// Wait for the pin to undergo a transition from high to low. #[inline] pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { self.flex.wait_for_falling_edge() } /// Wait for the pin to undergo any transition, i.e low to high OR high to low. #[inline] pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { self.flex.wait_for_any_edge() } } impl embedded_hal_async::digital::Wait for Input<'_, Async> { 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 embedded_hal_async::digital::Wait for Flex<'_, Async> { 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(()) } } // Both embedded_hal 0.2 and 1.0 must be supported by embassy HALs. impl embedded_hal_02::digital::v2::InputPin for Flex<'_, M> { // GPIO operations on this block cannot fail, therefor we set the error type // to Infallible to guarantee that we can only produce Ok variants. type Error = Infallible; #[inline] fn is_high(&self) -> Result { Ok(self.is_high()) } #[inline] fn is_low(&self) -> Result { Ok(self.is_low()) } } impl embedded_hal_02::digital::v2::InputPin for Input<'_, M> { type Error = Infallible; #[inline] fn is_high(&self) -> Result { Ok(self.is_high()) } #[inline] fn is_low(&self) -> Result { Ok(self.is_low()) } } impl embedded_hal_02::digital::v2::OutputPin for Output<'_> { 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 embedded_hal_02::digital::v2::OutputPin for Flex<'_, M> { 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 embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'_, M> { #[inline] fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } #[inline] fn is_set_low(&self) -> Result { Ok(self.is_set_low()) } } impl embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'_, M> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { self.toggle(); Ok(()) } } impl embedded_hal_1::digital::ErrorType for Flex<'_, M> { type Error = Infallible; } impl embedded_hal_1::digital::ErrorType for Input<'_, M> { type Error = Infallible; } impl embedded_hal_1::digital::ErrorType for Output<'_> { type Error = Infallible; } impl embedded_hal_1::digital::InputPin for Input<'_, M> { #[inline] fn is_high(&mut self) -> Result { Ok((*self).is_high()) } #[inline] fn is_low(&mut self) -> Result { Ok((*self).is_low()) } } impl embedded_hal_1::digital::OutputPin for Output<'_> { #[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 embedded_hal_1::digital::OutputPin for Flex<'_, M> { #[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 embedded_hal_1::digital::StatefulOutputPin for Flex<'_, M> { #[inline] fn is_set_high(&mut self) -> Result { Ok((*self).is_set_high()) } #[inline] fn is_set_low(&mut self) -> Result { Ok((*self).is_set_low()) } } ================================================ FILE: embassy-mcxa/src/i2c/controller.rs ================================================ //! # LPI2C Controller Driver //! //! This module provides a driver for the Low-Power Inter-Integrated //! Circuit (LPI2C) controller, supporting blocking, //! interrupt-only async, and DMA async modes of operation. //! //! The driver support all transfer speeds except for Fast Mode+. //! //! ## Features //! //! - **Blocking and Asynchronous Modes**: Supports both blocking and //! async APIs for flexibility in different runtime environments. //! - **DMA Support**: Enables high-performance data transfers using //! DMA. //! - **Configurable Bus Speeds**: Supports standard (100 kHz), fast //! (400 kHz), and fast-plus (1 MHz) modes. Ultra-fast (3.4 MHz) mode //! is not yet implemented. //! - **Error Handling**: Comprehensive error reporting, including //! FIFO errors, arbitration loss, and address NACK conditions. //! - **Embedded HAL Compatibility**: Implements traits from //! `embedded-hal` and `embedded-hal-async` for interoperability with //! other libraries. //! //! ### Error Types //! //! - `SetupError`: Errors related to hardware initialization, such as //! clock configuration issues. //! - `IOError`: Errors during I2C operations, including FIFO errors, //! arbitration loss, and invalid buffer lengths. //! //! ## Example //! //! ```rust,no_run //! #![no_std] //! #![no_main] //! //! # extern crate panic_halt; //! # extern crate embassy_mcxa; //! # extern crate embassy_executor; //! # use panic_halt as _; //! use embassy_executor::Spawner; //! use embassy_mcxa::clocks::config::Div8; //! use embassy_mcxa::config::Config; //! use embassy_mcxa::i2c::controller::{self, I2c, Speed}; //! //! #[embassy_executor::main] //! async fn main(_spawner: Spawner) { //! let mut config = Config::default(); //! config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); //! //! let p = embassy_mcxa::init(config); //! //! let mut i2c = I2c::new_blocking(p.LPI2C2, p.P1_9, p.P1_8, Default::default()).unwrap(); //! //! // Write data //! i2c.blocking_write(0x50, &[0x01, 0x02, 0x03]).unwrap(); //! //! // Read data //! let mut buffer = [0u8; 3]; //! i2c.blocking_read(0x50, &mut buffer).unwrap(); //! } //! ``` use core::future::Future; use core::marker::PhantomData; use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; use super::{Async, AsyncMode, Blocking, Dma, Info, Instance, Mode, SclPin, SdaPin}; use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; use crate::clocks::{ClockError, PoweredClock, WakeGuard, enable_and_reset}; use crate::dma::{Channel, DMA_MAX_TRANSFER_SIZE, DmaChannel, TransferOptions}; use crate::gpio::{AnyPin, SealedPin}; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::pac::lpi2c::regs::Msr; use crate::pac::lpi2c::vals::{Alf, Cmd, Dmf, Dozen, Epf, McrRrf, McrRtf, MsrFef, MsrSdf, Ndf, Pltf, Stf}; /// Errors exclusive to HW initialization #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum SetupError { /// Clock configuration error. ClockSetup(ClockError), /// Other internal errors or unexpected state. Other, } /// I/O Errors #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum IOError { /// FIFO Error, the command in the FIFO queue expected the controller to be in a STARTed state, but it was not. /// /// Even though a START could have been issued earlier, the controller might now be in a different state. /// For example, a NAK condition was detected and the controller automatically issued a STOP. FifoError, /// Reading for I2C failed. ReadFail, /// Writing to I2C failed. WriteFail, /// I2C address NAK condition. AddressNack, /// Bus level arbitration loss. ArbitrationLoss, /// Address out of range. AddressOutOfRange(u8), /// Invalid write buffer length. InvalidWriteBufferLength, /// Invalid read buffer length. InvalidReadBufferLength, /// Other internal errors or unexpected state. Other, } impl From for IOError { fn from(_value: crate::dma::InvalidParameters) -> Self { IOError::Other } } /// I2C interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); if T::info().regs().mier().read().0 != 0 { T::info().regs().mier().write(|w| { w.set_tdie(false); w.set_rdie(false); w.set_epie(false); w.set_sdie(false); w.set_ndie(false); w.set_alie(false); w.set_feie(false); w.set_pltie(false); w.set_dmie(false); w.set_stie(false); }); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } /// Bus speed (nominal SCL, no clock stretching) #[derive(Clone, Copy, Default, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Speed { #[default] /// 100 kbit/sec Standard, /// 400 kbit/sec Fast, /// 1 Mbit/sec FastPlus, /// 3.4 Mbit/sec UltraFast, } impl From for u32 { fn from(val: Speed) -> Self { match val { Speed::Standard => 100_000, Speed::Fast => 400_000, Speed::FastPlus => 1_000_000, Speed::UltraFast => 3_400_000, } } } impl From for (u8, u8, u8, u8) { fn from(value: Speed) -> (u8, u8, u8, u8) { match value { Speed::Standard => (0x3d, 0x37, 0x3b, 0x1d), Speed::Fast => (0x0e, 0x0c, 0x0d, 0x06), Speed::FastPlus => (0x04, 0x03, 0x03, 0x02), // UltraFast is "special". Leaving it unimplemented until // the driver and the clock API is further stabilized. Speed::UltraFast => todo!(), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum SendStop { No, Yes, } /// I2C controller configuration #[derive(Clone, Copy, Default)] #[non_exhaustive] pub struct Config { /// Bus speed pub speed: Speed, /// Clock configuration pub clock_config: ClockConfig, } /// I2C controller clock configuration #[derive(Clone, Copy)] #[non_exhaustive] pub struct ClockConfig { /// Powered clock configuration pub power: PoweredClock, /// LPI2C clock source pub source: Lpi2cClockSel, /// LPI2C pre-divider pub div: Div4, } impl Default for ClockConfig { fn default() -> Self { Self { power: PoweredClock::NormalEnabledDeepSleepDisabled, source: Lpi2cClockSel::FroLfDiv, div: const { Div4::no_div() }, } } } /// I2C Controller Driver. pub struct I2c<'d, M: Mode> { info: &'static Info, _scl: Peri<'d, AnyPin>, _sda: Peri<'d, AnyPin>, mode: M, is_hs: bool, _wg: Option, } impl<'d> I2c<'d, Blocking> { /// Creates a new blocking instance of the I2C Controller bus driver. /// /// This method initializes the I2C controller in blocking mode, allowing /// synchronous read and write operations. The I2C bus is configured based /// on the provided `Config` structure, which specifies parameters such as /// bus speed and clock settings. /// /// # Arguments /// /// - `peri`: The peripheral instance representing the I2C controller hardware. /// - `scl`: The pin to be used for the I2C clock line (SCL). /// - `sda`: The pin to be used for the I2C data line (SDA). /// - `config`: A `Config` structure specifying the desired I2C configuration, including bus speed and clock settings. /// /// # Returns /// /// - `Ok(Self)`: A new instance of the I2C driver in blocking mode if initialization is successful. /// - `Err(SetupError)`: An error if the initialization fails, such as due to invalid clock configuration. /// /// # Behavior /// /// - The I2C controller is configured and enabled based on the provided `Config`. /// - Any external pins used for SCL and SDA will be placed into a disabled state when the driver instance is dropped. /// /// # Errors /// /// - `SetupError::ClockSetup`: If there is an issue with the clock configuration. /// - `SetupError::Other`: For other unexpected initialization errors. pub fn new_blocking( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, ) -> Result { Self::new_inner(peri, scl, sda, config, Blocking) } } impl<'d, M: Mode> I2c<'d, M> { fn new_inner( _peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, mode: M, ) -> Result { let ClockConfig { power, source, div } = config.clock_config; // Enable clocks let conf = Lpi2cConfig { power, source, div, instance: T::CLOCK_INSTANCE, }; let parts = unsafe { enable_and_reset::(&conf).map_err(SetupError::ClockSetup)? }; scl.mux(); sda.mux(); let _scl = scl.into(); let _sda = sda.into(); let inst = Self { info: T::info(), _scl, _sda, mode, is_hs: config.speed == Speed::UltraFast, _wg: parts.wake_guard, }; inst.set_configuration(&config); Ok(inst) } fn set_configuration(&self, config: &Config) { // Disable the controller. critical_section::with(|_| self.info.regs().mcr().modify(|w| w.set_men(false))); // Soft-reset the controller, read and write FIFOs. self.reset_fifos(); critical_section::with(|_| { self.info.regs().mcr().modify(|w| w.set_rst(true)); // According to Reference Manual section 40.7.1.4, "There // is no minimum delay required before clearing the // software reset", therefore we clear it immediately. self.info.regs().mcr().modify(|w| w.set_rst(false)); self.info.regs().mcr().modify(|w| { w.set_dozen(Dozen::ENABLED); w.set_dbgen(false); }); }); let (clklo, clkhi, sethold, datavd) = config.speed.into(); critical_section::with(|_| { self.info.regs().mccr0().modify(|w| { w.set_clklo(clklo); w.set_clkhi(clkhi); w.set_sethold(sethold); w.set_datavd(datavd); }) }); // Enable the controller. critical_section::with(|_| self.info.regs().mcr().modify(|w| w.set_men(true))); // Clear all flags self.info.regs().msr().write(|w| { w.set_epf(Epf::INT_YES); w.set_sdf(MsrSdf::INT_YES); w.set_ndf(Ndf::INT_YES); w.set_alf(Alf::INT_YES); w.set_fef(MsrFef::INT_YES); w.set_pltf(Pltf::INT_YES); w.set_dmf(Dmf::INT_YES); w.set_stf(Stf::INT_YES); }); } fn remediation(&self) { #[cfg(feature = "defmt")] defmt::trace!("Future dropped, issuing stop",); // if the FIFO is not empty, drop its contents. if !self.is_tx_fifo_empty_or_error() { self.reset_fifos(); } // send a stop command let _ = self.stop(); } /// Resets both TX and RX FIFOs dropping their contents. fn reset_fifos(&self) { critical_section::with(|_| { self.info.regs().mcr().modify(|w| { w.set_rtf(McrRtf::RESET); w.set_rrf(McrRrf::RESET); }); }); } /// Checks whether the TX FIFO is full fn is_tx_fifo_full(&self) -> bool { let txfifo_size = 1 << self.info.regs().param().read().mtxfifo(); self.info.regs().mfsr().read().txcount() == txfifo_size } /// Checks whether the TX FIFO is empty fn is_tx_fifo_empty(&self) -> bool { self.info.regs().mfsr().read().txcount() == 0 } /// Checks whether the TX FIFO or if there is an error condition active. fn is_tx_fifo_empty_or_error(&self) -> bool { self.is_tx_fifo_empty() || self.status().is_err() } /// Checks whether the RX FIFO is empty. fn is_rx_fifo_empty(&self) -> bool { self.info.regs().mfsr().read().rxcount() == 0 } /// Parses the controller status producing an /// appropriate `Result<(), Error>` variant. fn parse_status(&self, msr: &Msr) -> Result<(), IOError> { if msr.ndf() == Ndf::INT_YES { Err(IOError::AddressNack) } else if msr.alf() == Alf::INT_YES { Err(IOError::ArbitrationLoss) } else if msr.fef() == MsrFef::INT_YES { Err(IOError::FifoError) } else { Ok(()) } } /// Reads, parses and clears the controller status producing an /// appropriate `Result<(), Error>` variant. /// /// Will also send a STOP command if the tx_fifo is empty. fn status_and_act(&self) -> Result<(), IOError> { let msr = self.info.regs().msr().read(); self.info.regs().msr().write(|w| *w = msr); let status = self.parse_status(&msr); if let Err(IOError::AddressNack) = status { // According to the Reference Manual, section 40.7.1.5 // Controller Status (MSR), the controller will // automatically send a STOP condition if // `MCFGR1[AUTOSTOP]` is enabled or if the transmit FIFO // is *not* empty. // // If neither of those conditions is true, we will send a // STOP ourselves. if !self.info.regs().mcfgr1().read().autostop() && self.is_tx_fifo_empty() { self.remediation(); } } status } /// Reads and parses the controller status producing an /// appropriate `Result<(), Error>` variant. fn status(&self) -> Result<(), IOError> { self.parse_status(&self.info.regs().msr().read()) } /// Inserts the given command into the outgoing FIFO. /// /// Caller must ensure there is space in the FIFO for the new /// command. fn send_cmd(&self, cmd: Cmd, data: u8) { #[cfg(feature = "defmt")] defmt::trace!( "Sending cmd '{}' ({}) with data '{:08x}' MSR: {:08x}", cmd, cmd as u8, data, self.info.regs().msr().read() ); self.info.regs().mtdr().write(|w| { w.set_data(data); w.set_cmd(cmd); }); } /// Prepares an appropriate Start condition on bus by issuing a /// `Start` command together with the device address and R/w bit. /// /// Blocks waiting for space in the FIFO to become available, then /// sends the command and blocks waiting for the FIFO to become /// empty ensuring the command was sent. fn start(&self, address: u8, read: bool) -> Result<(), IOError> { if address >= 0x80 { return Err(IOError::AddressOutOfRange(address)); } // Wait until we have space in the TxFIFO while self.is_tx_fifo_full() {} let addr_rw = address << 1 | if read { 1 } else { 0 }; self.send_cmd(if self.is_hs { Cmd::START_HS } else { Cmd::START }, addr_rw); // Wait for TxFIFO to be drained while !self.is_tx_fifo_empty_or_error() {} // Check controller status self.status_and_act() } /// Prepares a Stop condition on the bus. /// /// Analogous to `start`, this blocks waiting for space in the /// FIFO to become available, then sends the command and blocks /// waiting for the FIFO to become empty ensuring the command was /// sent. fn stop(&self) -> Result<(), IOError> { // Wait until we have space in the TxFIFO while self.is_tx_fifo_full() {} self.send_cmd(Cmd::STOP, 0); // Wait for TxFIFO to be drained while !self.is_tx_fifo_empty_or_error() {} self.status_and_act() } fn blocking_read_internal(&self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<(), IOError> { if read.is_empty() { return Err(IOError::InvalidReadBufferLength); } for chunk in read.chunks_mut(256) { self.start(address, true)?; // Wait until we have space in the TxFIFO while self.is_tx_fifo_full() {} self.send_cmd(Cmd::RECEIVE, (chunk.len() - 1) as u8); for byte in chunk.iter_mut() { // Wait until there's data in the RxFIFO while self.is_rx_fifo_empty() {} *byte = self.info.regs().mrdr().read().data(); } } if send_stop == SendStop::Yes { self.stop()?; } Ok(()) } fn blocking_write_internal(&self, address: u8, write: &[u8], send_stop: SendStop) -> Result<(), IOError> { self.start(address, false)?; // Usually, embassy HALs error out with an empty write, // however empty writes are useful for writing I2C scanning // logic through write probing. That is, we send a start with // R/w bit cleared, but instead of writing any data, just send // the stop onto the bus. This has the effect of checking if // the resulting address got an ACK but causing no // side-effects to the device on the other end. // // Because of this, we are not going to error out in case of // empty writes. if write.is_empty() { #[cfg(feature = "defmt")] defmt::trace!("Empty write, write probing?"); if send_stop == SendStop::Yes { self.stop()?; } return Ok(()); } for byte in write { // Wait until we have space in the TxFIFO while self.is_tx_fifo_full() {} self.send_cmd(Cmd::TRANSMIT, *byte); } if send_stop == SendStop::Yes { self.stop()?; } Ok(()) } // Public API: Blocking /// Reads data from the specified I2C address into the provided buffer. /// /// This method blocks the caller until the operation is complete. /// /// # Arguments /// /// - `address`: The 7-bit I2C address of the target device. /// - `read`: A mutable buffer to store the data read from the device. /// /// # Returns /// /// - `Ok(())` if the read operation is successful. /// - `Err(IOError)` if an error occurs during the operation, such as an address NACK or FIFO error. /// /// # Errors /// /// - `IOError::AddressNack`: If the device does not acknowledge the address. /// - `IOError::FifoError`: If there is an issue with the FIFO queue. /// - Other variants of `IOError` for specific I2C errors. /// /// # Notes /// /// The driver will attempt to fill the buffer with data. If the /// buffer length exceeds the maximum transfer size of the /// controller, the read operation will be performed in multiple /// chunks. This will be transparent to the caller. pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), IOError> { self.blocking_read_internal(address, read, SendStop::Yes) } /// Writes data to the specified I2C address from the provided buffer. /// /// This method blocks the caller until the operation is complete. /// /// # Arguments /// /// - `address`: The 7-bit I2C address of the target device. /// - `write`: A buffer containing the data to be written to the device. /// /// # Returns /// /// - `Ok(())` if the write operation is successful. /// - `Err(IOError)` if an error occurs during the operation, such as an address NACK or FIFO error. /// /// # Errors /// /// - `IOError::AddressNack`: If the device does not acknowledge the address. /// - `IOError::FifoError`: If there is an issue with the FIFO queue. /// - Other variants of `IOError` for specific I2C errors. pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), IOError> { self.blocking_write_internal(address, write, SendStop::Yes) } /// Performs a combined write and read operation on the specified I2C /// address. /// /// This method first writes data to the device, then reads data from the /// device into the provided buffer. The caller is blocked until the /// operation is complete. /// /// # Arguments /// /// - `address`: The 7-bit I2C address of the target device. /// - `write`: A buffer containing the data to be written to the device. /// - `read`: A mutable buffer to store the data read from the device. /// /// # Returns /// /// - `Ok(())` if the write-read operation is successful. /// - `Err(IOError)` if an error occurs during the operation, such as an address NACK or FIFO error. /// /// # Errors /// /// - `IOError::AddressNack`: If the device does not acknowledge the address. /// - `IOError::FifoError`: If there is an issue with the FIFO queue. /// - Other variants of `IOError` for specific I2C errors. pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), IOError> { self.blocking_write_internal(address, write, SendStop::No)?; self.blocking_read_internal(address, read, SendStop::Yes) } } #[allow(private_bounds)] impl<'d, M: AsyncMode> I2c<'d, M> where Self: AsyncEngine, { fn enable_rx_ints(&self) { self.info.regs().mier().write(|w| { w.set_rdie(true); w.set_ndie(true); w.set_alie(true); w.set_feie(true); w.set_pltie(true); }); } fn enable_tx_ints(&self) { self.info.regs().mier().write(|w| { w.set_tdie(true); w.set_ndie(true); w.set_alie(true); w.set_feie(true); w.set_pltie(true); }); } /// Schedule sending a START command and await it being pulled from the FIFO. /// /// Does not indicate that the command was responded to. async fn async_start(&self, address: u8, read: bool) -> Result<(), IOError> { if address >= 0x80 { return Err(IOError::AddressOutOfRange(address)); } // send the start command let addr_rw = address << 1 | if read { 1 } else { 0 }; self.send_cmd(if self.is_hs { Cmd::START_HS } else { Cmd::START }, addr_rw); self.info .wait_cell() .wait_for(|| { // enable interrupts self.enable_tx_ints(); // if the command FIFO is empty, we're done sending start self.is_tx_fifo_empty_or_error() }) .await .map_err(|_| IOError::Other)?; // Note: the START + ACK/NACK have not necessarily been finished here. // thus this might return Ok(()), but might at a later state result in NAK or FifoError. self.status_and_act() } async fn async_stop(&self) -> Result<(), IOError> { // send the stop command self.send_cmd(Cmd::STOP, 0); self.info .wait_cell() .wait_for(|| { // enable interrupts self.enable_tx_ints(); // if the command FIFO is empty, we're done sending stop self.is_tx_fifo_empty_or_error() }) .await .map_err(|_| IOError::Other)?; self.status_and_act() } // Public API: Async /// Reads data from the specified I2C address into the provided buffer asynchronously. /// /// This method performs the read operation without blocking the caller, /// returning a `Future` that resolves when the operation is complete. /// /// # Arguments /// /// - `address`: The 7-bit I2C address of the target device. /// - `read`: A mutable buffer to store the data read from the device. /// /// # Returns /// /// - A `Future` that resolves to `Ok(())` if the read operation is successful. /// - Resolves to `Err(IOError)` if an error occurs during the operation, such as an address NACK or FIFO error. /// /// # Errors /// /// - `IOError::AddressNack`: If the device does not acknowledge the address. /// - `IOError::FifoError`: If there is an issue with the FIFO queue. /// - Other variants of `IOError` for specific I2C errors. pub fn async_read<'a>( &'a mut self, address: u8, read: &'a mut [u8], ) -> impl Future> + 'a { ::async_read_internal(self, address, read, SendStop::Yes) } /// Writes data to the specified I2C address from the provided buffer asynchronously. /// /// This method performs the write operation without blocking the caller, returning a `Future` that resolves when the operation is complete. /// /// # Arguments /// /// - `address`: The 7-bit I2C address of the target device. /// - `write`: A buffer containing the data to be written to the device. /// /// # Returns /// /// - A `Future` that resolves to `Ok(())` if the write operation is successful. /// - Resolves to `Err(IOError)` if an error occurs during the operation, such as an address NACK or FIFO error. /// /// # Errors /// /// - `IOError::AddressNack`: If the device does not acknowledge the address. /// - `IOError::FifoError`: If there is an issue with the FIFO queue. /// - Other variants of `IOError` for specific I2C errors. pub fn async_write<'a>( &'a mut self, address: u8, write: &'a [u8], ) -> impl Future> + 'a { ::async_write_internal(self, address, write, SendStop::Yes) } /// Performs a combined write and read operation on the specified I2C /// address asynchronously. /// /// This method first writes data to the device, then reads data from the /// device into the provided buffer. The operation is performed without /// blocking the caller. /// /// # Arguments /// /// - `address`: The 7-bit I2C address of the target device. /// - `write`: A buffer containing the data to be written to the device. /// - `read`: A mutable buffer to store the data read from the device. /// /// # Returns /// /// - `Ok(())` if the write-read operation is successful. /// - `Err(IOError)` if an error occurs during the operation, such as an address NACK or FIFO error. /// /// # Errors /// /// - `IOError::AddressNack`: If the device does not acknowledge the address. /// - `IOError::FifoError`: If there is an issue with the FIFO queue. /// - Other variants of `IOError` for specific I2C errors. pub async fn async_write_read<'a>( &'a mut self, address: u8, write: &'a [u8], read: &'a mut [u8], ) -> Result<(), IOError> { ::async_write_internal(self, address, write, SendStop::No).await?; ::async_read_internal(self, address, read, SendStop::Yes).await } } trait AsyncEngine { fn async_read_internal<'a>( &'a mut self, address: u8, read: &'a mut [u8], send_stop: SendStop, ) -> impl Future> + 'a; fn async_write_internal<'a>( &'a mut self, address: u8, write: &'a [u8], send_stop: SendStop, ) -> impl Future> + 'a; } impl<'d> I2c<'d, Async> { /// Creates a new interrupt-only asynchronous instance of the I2C Controller /// bus driver. /// /// This method initializes the I2C controller in asynchronous mode, /// enabling non-blocking operations using futures. The I2C bus is /// configured based on the provided `Config` structure, which specifies /// parameters such as bus speed and clock settings. /// /// # Arguments /// /// - `peri`: The peripheral instance representing the I2C controller hardware. /// - `scl`: The pin to be used for the I2C clock line (SCL). /// - `sda`: The pin to be used for the I2C data line (SDA). /// - `_irq`: The interrupt binding for the I2C controller, ensuring that an interrupt handler is registered. /// - `config`: A `Config` structure specifying the desired I2C configuration, including bus speed and clock settings. /// /// # Returns /// /// - `Ok(Self)`: A new instance of the I2C driver in asynchronous mode if initialization is successful. /// - `Err(SetupError)`: An error if the initialization fails, such as due to invalid clock configuration. /// /// # Behavior /// /// - The I2C controller is configured and enabled based on the provided `Config`. /// - The interrupt for the I2C controller is enabled to support asynchronous operations. /// - Any external pins used for SCL and SDA will be placed into a disabled state when the driver instance is dropped. /// /// # Errors /// /// - `SetupError::ClockSetup`: If there is an issue with the clock configuration. /// - `SetupError::Other`: For other unexpected initialization errors. pub fn new_async( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable() }; Self::new_inner(peri, scl, sda, config, Async) } } impl<'d> AsyncEngine for I2c<'d, Async> { async fn async_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<(), IOError> { if read.is_empty() { return Err(IOError::InvalidReadBufferLength); } // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| self.remediation()); for chunk in read.chunks_mut(256) { self.async_start(address, true).await?; // send receive command self.send_cmd(Cmd::RECEIVE, (chunk.len() - 1) as u8); self.info .wait_cell() .wait_for(|| { // enable interrupts self.enable_tx_ints(); // if the command FIFO is empty, we're done sending start self.is_tx_fifo_empty_or_error() }) .await .map_err(|_| IOError::Other)?; for byte in chunk.iter_mut() { self.info .wait_cell() .wait_for(|| { // enable interrupts self.enable_rx_ints(); // if the rx FIFO is not empty, we need to read a byte !self.is_rx_fifo_empty() }) .await .map_err(|_| IOError::ReadFail)?; *byte = self.info.regs().mrdr().read().data(); } } if send_stop == SendStop::Yes { self.async_stop().await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } async fn async_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<(), IOError> { self.async_start(address, false).await?; // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| self.remediation()); // Usually, embassy HALs error out with an empty write, // however empty writes are useful for writing I2C scanning // logic through write probing. That is, we send a start with // R/w bit cleared, but instead of writing any data, just send // the stop onto the bus. This has the effect of checking if // the resulting address got an ACK but causing no // side-effects to the device on the other end. // // Because of this, we are not going to error out in case of // empty writes. if write.is_empty() { #[cfg(feature = "defmt")] defmt::trace!("Empty write, write probing?"); if send_stop == SendStop::Yes { self.async_stop().await?; } return Ok(()); } for byte in write { // initiate transmit self.send_cmd(Cmd::TRANSMIT, *byte); self.info .wait_cell() .wait_for(|| { // enable interrupts self.enable_tx_ints(); // if the tx FIFO is empty, we're done transmiting self.is_tx_fifo_empty_or_error() }) .await .map_err(|_| IOError::WriteFail)?; self.status_and_act()?; } if send_stop == SendStop::Yes { self.async_stop().await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } } impl<'d> I2c<'d, Dma<'d>> { /// Creates a new asynchronous instance of the I2C Controller bus driver with DMA support. /// /// This method initializes the I2C controller in asynchronous mode with /// Direct Memory Access (DMA) support, enabling efficient non-blocking /// operations for large data transfers. The I2C bus is configured based on /// the provided `Config` structure, which specifies parameters such as bus /// speed and clock settings. /// /// # Arguments /// /// - `peri`: The peripheral instance representing the I2C controller hardware. /// - `scl`: The pin to be used for the I2C clock line (SCL). /// - `sda`: The pin to be used for the I2C data line (SDA). /// - `tx_dma`: The DMA channel to be used for transmitting data. /// - `rx_dma`: The DMA channel to be used for receiving data. /// - `_irq`: The interrupt binding for the I2C controller, ensuring that an interrupt handler is registered. /// - `config`: A `Config` structure specifying the desired I2C configuration, including bus speed and clock settings. /// /// # Returns /// /// - `Ok(Self)`: A new instance of the I2C driver in asynchronous mode with DMA support if initialization is successful. /// - `Err(SetupError)`: An error if the initialization fails, such as due to invalid clock configuration. /// /// # Behavior /// /// - The I2C controller is configured and enabled based on the provided `Config`. /// - The interrupt for the I2C controller is enabled to support asynchronous operations. /// - The specified DMA channels are initialized and their interrupts are enabled. /// - Any external pins used for SCL and SDA will be placed into a disabled state when the driver instance is dropped. /// /// # Errors /// /// - `SetupError::ClockSetup`: If there is an issue with the clock configuration. /// - `SetupError::Other`: For other unexpected initialization errors. pub fn new_async_with_dma( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, tx_dma: Peri<'d, impl Channel>, rx_dma: Peri<'d, impl Channel>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable() }; // enable this channel's interrupt let tx_dma = DmaChannel::new(tx_dma); let rx_dma = DmaChannel::new(rx_dma); tx_dma.enable_interrupt(); rx_dma.enable_interrupt(); Self::new_inner( peri, scl, sda, config, Dma { tx_dma, rx_dma, tx_request: T::TX_DMA_REQUEST, rx_request: T::RX_DMA_REQUEST, }, ) } } impl<'d> AsyncEngine for I2c<'d, Dma<'d>> { async fn async_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<(), IOError> { if read.is_empty() { return Err(IOError::InvalidReadBufferLength); } // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.remediation(); self.info.regs().mder().modify(|w| w.set_rdde(false)); }); for chunk in read.chunks_mut(256) { self.async_start(address, true).await?; // send receive command self.send_cmd(Cmd::RECEIVE, (chunk.len() - 1) as u8); let peri_addr = self.info.regs().mrdr().as_ptr() as *const u8; // _rx_dma is guaranteed to be Some unsafe { // Clean up channel state self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.rx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.rx_dma.set_request_source(self.mode.rx_request); // Configure TCD for peripheral-to-memory transfer self.mode.rx_dma.setup_read_from_peripheral( peri_addr, chunk, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Enable I2C RX DMA request self.info.regs().mder().modify(|w| w.set_rdde(true)); // Enable DMA channel request self.mode.rx_dma.enable_request(); } // Wait for completion asynchronously core::future::poll_fn(|cx| { self.mode.rx_dma.waker().register(cx.waker()); if self.mode.rx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Ensure DMA writes are visible to CPU cortex_m::asm::dsb(); // Cleanup self.info.regs().mder().modify(|w| w.set_rdde(false)); unsafe { self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); } } if send_stop == SendStop::Yes { self.async_stop().await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } async fn async_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<(), IOError> { self.async_start(address, false).await?; // Usually, embassy HALs error out with an empty write, // however empty writes are useful for writing I2C scanning // logic through write probing. That is, we send a start with // R/w bit cleared, but instead of writing any data, just send // the stop onto the bus. This has the effect of checking if // the resulting address got an ACK but causing no // side-effects to the device on the other end. // // Because of this, we are not going to error out in case of // empty writes. if write.is_empty() { #[cfg(feature = "defmt")] defmt::trace!("Empty write, write probing?"); if send_stop == SendStop::Yes { self.async_stop().await?; } return Ok(()); } // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.remediation(); self.info.regs().mder().modify(|w| w.set_tdde(false)); }); for chunk in write.chunks(DMA_MAX_TRANSFER_SIZE) { let peri_addr = self.info.regs().mtdr().as_ptr() as *mut u8; unsafe { // Clean up channel state self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.tx_dma.set_request_source(self.mode.tx_request); // Configure TCD for memory-to-peripheral transfer self.mode.tx_dma.setup_write_to_peripheral( chunk, peri_addr, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Enable I2C TX DMA request self.info.regs().mder().modify(|w| w.set_tdde(true)); // Enable DMA channel request self.mode.tx_dma.enable_request(); } // Wait for completion asynchronously core::future::poll_fn(|cx| { self.mode.tx_dma.waker().register(cx.waker()); if self.mode.tx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Ensure DMA writes are visible to CPU cortex_m::asm::dsb(); // Cleanup self.info.regs().mder().modify(|w| w.set_tdde(false)); unsafe { self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } } if send_stop == SendStop::Yes { self.async_stop().await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } } impl<'d, M: Mode> Drop for I2c<'d, M> { fn drop(&mut self) { self._scl.set_as_disabled(); self._sda.set_as_disabled(); } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, M> { type Error = IOError; 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 = IOError; fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { self.blocking_write(address, bytes) } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, M> { type Error = IOError; fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { self.blocking_write_read(address, bytes, buffer) } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, M> { type Error = IOError; fn exec( &mut self, address: u8, operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>], ) -> Result<(), Self::Error> { if let Some((last, rest)) = operations.split_last_mut() { for op in rest { match op { embedded_hal_02::blocking::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, SendStop::No)? } embedded_hal_02::blocking::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, SendStop::No)? } } } match last { embedded_hal_02::blocking::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, SendStop::Yes) } embedded_hal_02::blocking::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, SendStop::Yes) } } } else { Ok(()) } } } impl embedded_hal_1::i2c::Error for IOError { fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { match *self { Self::ArbitrationLoss => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, Self::AddressNack => { embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) } _ => embedded_hal_1::i2c::ErrorKind::Other, } } } impl<'d, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, M> { type Error = IOError; } impl<'d, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, M> { fn transaction( &mut self, address: u8, operations: &mut [embedded_hal_1::i2c::Operation<'_>], ) -> Result<(), Self::Error> { if let Some((last, rest)) = operations.split_last_mut() { for op in rest { match op { embedded_hal_1::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, SendStop::No)? } embedded_hal_1::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, SendStop::No)? } } } match last { embedded_hal_1::i2c::Operation::Read(buf) => self.blocking_read_internal(address, buf, SendStop::Yes), embedded_hal_1::i2c::Operation::Write(buf) => self.blocking_write_internal(address, buf, SendStop::Yes), } } else { Ok(()) } } } impl<'d, M: AsyncMode> embedded_hal_async::i2c::I2c for I2c<'d, M> where I2c<'d, M>: AsyncEngine, { async fn transaction( &mut self, address: u8, operations: &mut [embedded_hal_async::i2c::Operation<'_>], ) -> Result<(), Self::Error> { if let Some((last, rest)) = operations.split_last_mut() { for op in rest { match op { embedded_hal_async::i2c::Operation::Read(buf) => { ::async_read_internal(self, address, buf, SendStop::No).await? } embedded_hal_async::i2c::Operation::Write(buf) => { ::async_write_internal(self, address, buf, SendStop::No).await? } } } match last { embedded_hal_async::i2c::Operation::Read(buf) => { ::async_read_internal(self, address, buf, SendStop::Yes).await } embedded_hal_async::i2c::Operation::Write(buf) => { ::async_write_internal(self, address, buf, SendStop::Yes).await } } } else { Ok(()) } } } impl<'d, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, M> { type Config = Config; type ConfigError = SetupError; fn set_config(&mut self, config: &Self::Config) -> Result<(), SetupError> { self.set_configuration(config); Ok(()) } } ================================================ FILE: embassy-mcxa/src/i2c/mod.rs ================================================ //! I2C Support use embassy_hal_internal::PeripheralType; use maitake_sync::WaitCell; use paste::paste; use crate::clocks::Gate; use crate::clocks::periph_helpers::Lpi2cConfig; use crate::dma::{DmaChannel, DmaRequest}; use crate::gpio::{GpioPin, SealedPin}; use crate::{interrupt, pac}; pub mod controller; pub mod target; mod sealed { /// Seal a trait pub trait Sealed {} } trait SealedInstance: Gate { fn info() -> &'static Info; /// Clock instance const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); const TX_DMA_REQUEST: DmaRequest; const RX_DMA_REQUEST: DmaRequest; } /// I2C Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this I2C instance. type Interrupt: interrupt::typelevel::Interrupt; } struct Info { regs: pac::lpi2c::Lpi2c, wait_cell: WaitCell, } impl Info { #[inline(always)] fn regs(&self) -> pac::lpi2c::Lpi2c { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } unsafe impl Sync for Info {} macro_rules! impl_instance { ($($n:literal),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], wait_cell: WaitCell::new(), }; &INFO } const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance = crate::clocks::periph_helpers::Lpi2cInstance::[]; const PERF_INT_INCR: fn() = crate::perf_counters::[]; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::[]; const TX_DMA_REQUEST: DmaRequest = DmaRequest::[]; const RX_DMA_REQUEST: DmaRequest = DmaRequest::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0, 1, 2, 3); /// SCL pin trait. pub trait SclPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// SDA pin trait. pub trait SdaPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// Driver mode. #[allow(private_bounds)] pub trait Mode: sealed::Sealed {} /// Async driver mode. #[allow(private_bounds)] pub trait AsyncMode: sealed::Sealed + Mode {} /// Blocking mode. pub struct Blocking; impl sealed::Sealed for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl sealed::Sealed for Async {} impl Mode for Async {} impl AsyncMode for Async {} /// DMA mode. pub struct Dma<'d> { tx_dma: DmaChannel<'d>, rx_dma: DmaChannel<'d>, rx_request: DmaRequest, tx_request: DmaRequest, } impl sealed::Sealed for Dma<'_> {} impl Mode for Dma<'_> {} impl AsyncMode for Dma<'_> {} macro_rules! impl_pin { ($pin:ident, $peri:ident, $fn:ident, $trait:ident) => { impl sealed::Sealed for crate::peripherals::$pin {} impl $trait for crate::peripherals::$pin { fn mux(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); self.set_function(crate::pac::port::vals::Mux::$fn); self.set_enable_input_buffer(true); } } }; } #[cfg(feature = "mcxa2xx")] mod mcxa2xx { use super::*; impl_pin!(P0_16, LPI2C0, MUX2, SdaPin); impl_pin!(P0_17, LPI2C0, MUX2, SclPin); impl_pin!(P0_18, LPI2C0, MUX2, SclPin); impl_pin!(P0_19, LPI2C0, MUX2, SdaPin); impl_pin!(P1_0, LPI2C1, MUX3, SdaPin); impl_pin!(P1_1, LPI2C1, MUX3, SclPin); impl_pin!(P1_2, LPI2C1, MUX3, SdaPin); impl_pin!(P1_3, LPI2C1, MUX3, SclPin); impl_pin!(P1_8, LPI2C2, MUX3, SdaPin); impl_pin!(P1_9, LPI2C2, MUX3, SclPin); impl_pin!(P1_10, LPI2C2, MUX3, SdaPin); impl_pin!(P1_11, LPI2C2, MUX3, SclPin); impl_pin!(P1_12, LPI2C1, MUX2, SdaPin); impl_pin!(P1_13, LPI2C1, MUX2, SclPin); impl_pin!(P1_14, LPI2C1, MUX2, SclPin); impl_pin!(P1_15, LPI2C1, MUX2, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_30, LPI2C0, MUX3, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_31, LPI2C0, MUX3, SclPin); impl_pin!(P3_27, LPI2C3, MUX2, SclPin); impl_pin!(P3_28, LPI2C3, MUX2, SdaPin); // impl_pin!(P3_29, LPI2C3, MUX2, HreqPin); What is this HREQ pin? impl_pin!(P3_30, LPI2C3, MUX2, SclPin); impl_pin!(P3_31, LPI2C3, MUX2, SdaPin); impl_pin!(P4_2, LPI2C2, MUX2, SdaPin); impl_pin!(P4_3, LPI2C0, MUX2, SclPin); impl_pin!(P4_4, LPI2C2, MUX2, SdaPin); impl_pin!(P4_5, LPI2C0, MUX2, SclPin); // impl_pin!(P4_6, LPI2C0, MUX2, HreqPin); What is this HREQ pin? } #[cfg(feature = "mcxa5xx")] mod mcxa5xx { use super::*; impl_pin!(P0_16, LPI2C0, MUX2, SdaPin); impl_pin!(P0_17, LPI2C0, MUX2, SclPin); impl_pin!(P0_18, LPI2C0, MUX2, SclPin); impl_pin!(P0_19, LPI2C0, MUX2, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_30, LPI2C0, MUX3, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_31, LPI2C0, MUX3, SclPin); impl_pin!(P1_0, LPI2C1, MUX3, SdaPin); impl_pin!(P1_1, LPI2C1, MUX3, SclPin); impl_pin!(P1_12, LPI2C1, MUX2, SdaPin); impl_pin!(P1_13, LPI2C1, MUX2, SclPin); impl_pin!(P1_14, LPI2C1, MUX2, SclPin); impl_pin!(P1_15, LPI2C1, MUX2, SdaPin); impl_pin!(P1_8, LPI2C2, MUX3, SdaPin); impl_pin!(P1_9, LPI2C2, MUX3, SclPin); impl_pin!(P4_2, LPI2C2, MUX2, SdaPin); impl_pin!(P4_3, LPI2C2, MUX2, SclPin); impl_pin!(P4_4, LPI2C2, MUX2, SdaPin); impl_pin!(P4_5, LPI2C2, MUX2, SclPin); impl_pin!(P3_20, LPI2C3, MUX2, SdaPin); impl_pin!(P3_21, LPI2C3, MUX2, SclPin); impl_pin!(P3_27, LPI2C3, MUX2, SclPin); impl_pin!(P3_28, LPI2C3, MUX2, SdaPin); impl_pin!(P3_30, LPI2C3, MUX2, SclPin); impl_pin!(P3_31, LPI2C3, MUX2, SdaPin); } ================================================ FILE: embassy-mcxa/src/i2c/target.rs ================================================ //! LPI2C Target Driver //! //! This module provides an implementation of an I2C target (slave) //! driver. It supports both blocking and asynchronous modes of //! operation, as well as DMA-based transfers. The driver allows the //! target device to respond to requests from an I2C controller //! (master), including reading and writing data, handling general //! calls, and responding to SMBus alerts. //! //! ## Example //! //! ```rust,no_run //! #![no_std] //! #![no_main] //! //! # extern crate panic_halt; //! # extern crate embassy_mcxa; //! # extern crate embassy_executor; //! # use panic_halt as _; //! use embassy_executor::Spawner; //! use embassy_mcxa::clocks::config::Div8; //! use embassy_mcxa::config::Config; //! use embassy_mcxa::i2c::target; //! //! #[embassy_executor::main] //! async fn main(_spawner: Spawner) { //! let mut config = Config::default(); //! config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); //! //! let p = embassy_mcxa::init(config); //! //! let mut config = target::Config::default(); //! config.address = target::Address::Dual(0x2a, 0x31); //! let mut i2c = target::I2c::new_blocking(p.LPI2C3, p.P3_27, p.P3_28, config).unwrap(); //! let mut buf = [0u8; 32]; //! //! loop { //! let request = i2c.blocking_listen().unwrap(); //! match request { //! target::Request::Read(addr) => { //! // Controller wants to read from us at `addr` //! buf.fill(0x55); //! let _count = i2c.blocking_respond_to_read(&buf).unwrap(); //! } //! target::Request::Write(_addr) => { //! // Controller wants to write to us at `addr` //! let _count = i2c.blocking_respond_to_write(&mut buf).unwrap(); //! } //! target::Request::Stop(_addr) => { //! // Controller issued a STOP condition for `addr` //! } //! target::Request::GeneralCall => { //! // Controller issued a General Call //! } //! target::Request::SmbusAlert => { //! // Controller issued an SMBus Alert //! } //! _ => {} //! } //! } //! } //! ``` use core::marker::PhantomData; use core::ops::Range; use core::sync::atomic::{Ordering, fence}; use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; use super::{Async, AsyncMode, Blocking, Dma, Info, Instance, Mode, SclPin, SdaPin}; pub use crate::clocks::PoweredClock; pub use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; use crate::clocks::{ClockError, WakeGuard, enable_and_reset}; use crate::dma::{Channel, DMA_MAX_TRANSFER_SIZE, DmaChannel, TransferOptions}; use crate::gpio::{AnyPin, SealedPin}; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::pac::lpi2c::vals::{Addrcfg, Filtdz, ScrRrf, ScrRtf}; /// Errors exclusive to hardware Initialization #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum SetupError { /// Clock configuration error. ClockSetup(ClockError), /// Invalid Address InvalidAddress, /// Other internal errors or unexpected state. Other, } /// Errors exclusive to I/O #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum IOError { /// Busy Busy BusBusy, /// Target Busy TargetBusy, /// FIFO Error FifoError, /// Bit Error BitError, /// Other internal errors or unexpected state. Other, } impl From for IOError { fn from(_value: crate::dma::InvalidParameters) -> Self { IOError::Other } } /// I2C interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); if T::info().regs().sier().read().0 != 0 { T::info().regs().sier().write(|w| { w.set_tdie(false); w.set_rdie(false); w.set_avie(false); w.set_taie(false); w.set_rsie(false); w.set_sdie(false); w.set_beie(false); w.set_feie(false); w.set_am0ie(false); w.set_am1ie(false); w.set_gcie(false); w.set_sarie(false); }); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } /// I2C target addresses. #[derive(Clone)] pub enum Address { /// Single address Single(u16), /// Two addresses Dual(u16, u16), /// Range of addresses Range(Range), } impl Default for Address { fn default() -> Self { Self::Single(0x2a) } } /// Enable or disable feature #[derive(Clone, Default)] pub enum Status { #[default] Disabled, Enabled, } impl From for bool { fn from(value: Status) -> Self { match value { Status::Disabled => false, Status::Enabled => true, } } } /// I2C target configuration #[derive(Clone, Default)] #[non_exhaustive] pub struct Config { /// Addresses to respond to pub address: Address, /// Enable SMBus alert pub smbus_alert: Status, /// Enable general call support pub general_call: Status, /// Clock configuration pub clock_config: ClockConfig, } /// I2C target clock configuration #[derive(Clone)] #[non_exhaustive] pub struct ClockConfig { /// Powered clock configuration pub power: PoweredClock, /// LPI2C clock source pub source: Lpi2cClockSel, /// LPI2C pre-divider pub div: Div4, } impl Default for ClockConfig { fn default() -> Self { Self { power: PoweredClock::NormalEnabledDeepSleepDisabled, source: Lpi2cClockSel::FroLfDiv, div: const { Div4::no_div() }, } } } /// I2C target events #[derive(Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Request { /// Controller wants to write data to this Target Write(u16), /// Controller wants to read data from this Target Read(u16), /// Controller issued Stop condition for this Target Stop(u16), /// Controller issued a General Call GeneralCall, /// Controller issued SMBUS Alert SmbusAlert, } /// I2C target events #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Event { SmbusAlert, GeneralCall, Address0Match(u16), Address1Match(u16), Stop(u16), RepeatedStart(u16), TransmitAck, AddressValid(u16), ReceiveData, TransmitData, } /// I2C Target Driver. pub struct I2c<'d, M: Mode> { info: &'static Info, _scl: Peri<'d, AnyPin>, _sda: Peri<'d, AnyPin>, smbus_alert: Status, general_call: Status, mode: M, _wg: Option, } impl<'d, M: Mode> I2c<'d, M> { fn new_inner( _peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, mode: M, ) -> Result { let ClockConfig { power, source, div } = config.clock_config; // Enable clocks let conf = Lpi2cConfig { power, source, div, instance: T::CLOCK_INSTANCE, }; let parts = unsafe { enable_and_reset::(&conf).map_err(SetupError::ClockSetup)? }; scl.mux(); sda.mux(); let _scl = scl.into(); let _sda = sda.into(); let inst = Self { info: T::info(), _scl, _sda, smbus_alert: config.smbus_alert.clone(), general_call: config.general_call.clone(), mode, _wg: parts.wake_guard, }; inst.set_configuration(&config)?; Ok(inst) } fn set_configuration(&self, config: &Config) -> Result<(), SetupError> { critical_section::with(|_| { // Disable the target. self.info.regs().scr().modify(|w| w.set_sen(false)); // Soft-reset the target, read and write FIFOs. self.reset_fifos(); self.info.regs().scr().modify(|w| w.set_rst(true)); // According to Reference Manual section 40.7.1.4, "There // is no minimum delay required before clearing the // software reset", therefore we clear it immediately. self.info.regs().scr().modify(|w| w.set_rst(false)); self.info.regs().scr().modify(|w| { w.set_filtdz(Filtdz::FILTER_DISABLED); w.set_filten(false); }); self.info.regs().scfgr1().modify(|w| { w.set_rxstall(true); w.set_txdstall(true); }); // Configure address matching match config.address { Address::Single(addr) => { self.info.regs().samr().write(|w| w.set_addr0(addr)); self.info.regs().scfgr1().modify(|w| { w.set_addrcfg(if (0x00..=0x7f).contains(&addr) { Addrcfg::ADDRESS_MATCH0_7_BIT } else { Addrcfg::ADDRESS_MATCH0_10_BIT }) }); } Address::Dual(addr0, addr1) => { // Either both a 7-bit or both are 10-bit if ((0x00..=0x7f).contains(&addr0) ^ (0x00..=0x7f).contains(&addr1)) || ((0x80..=0x3ff).contains(&addr0) ^ (0x80..=0x3ff).contains(&addr1)) { return Err(SetupError::InvalidAddress); } self.info.regs().samr().write(|w| { w.set_addr0(addr0); w.set_addr1(addr1); }); self.info.regs().scfgr1().modify(|w| { w.set_addrcfg(if (0x00..=0x7f).contains(&addr0) { Addrcfg::ADDRESS_MATCH0_7_BIT_OR_ADDRESS_MATCH1_7_BIT } else { Addrcfg::ADDRESS_MATCH0_10_BIT_OR_ADDRESS_MATCH1_10_BIT }) }); } Address::Range(Range { start, end }) => { if ((0x00..=0x7f).contains(&start) ^ (0x00..=0x7f).contains(&end)) || ((0x80..=0x3ff).contains(&start) ^ (0x80..=0x3ff).contains(&end)) { return Err(SetupError::InvalidAddress); } self.info.regs().samr().write(|w| { w.set_addr0(start); w.set_addr1(end - 1); }); self.info.regs().scfgr1().modify(|w| { w.set_addrcfg(if (0x00..=0x7f).contains(&start) { Addrcfg::FROM_ADDRESS_MATCH0_7_BIT_TO_ADDRESS_MATCH1_7_BIT } else { Addrcfg::FROM_ADDRESS_MATCH0_10_BIT_TO_ADDRESS_MATCH1_10_BIT }) }); } } // Enable the target. self.info.regs().scr().modify(|w| w.set_sen(true)); // Clear all flags self.info.regs().ssr().write(|w| { w.set_rsf(true); w.set_sdf(true); w.set_bef(true); w.set_fef(true); }); Ok(()) }) } /// Resets both TX and RX FIFOs dropping their contents. fn reset_fifos(&self) { // The critical section is needed to prevent an interrupt from // modifying SCR while we're in the middle of our // read-modify-write operation. critical_section::with(|_| { self.info.regs().scr().modify(|w| { w.set_rtf(ScrRtf::NOW_EMPTY); w.set_rrf(ScrRrf::NOW_EMPTY); }); }); } fn clear_status(&self) { self.info.regs().ssr().write(|w| { w.set_rsf(true); w.set_sdf(true); w.set_bef(true); w.set_fef(true); }); } /// Reads and parses the target status producing an /// appropriate `Result<(), Error>` variant. fn status(&self) -> Result { let ssr = self.info.regs().ssr().read(); self.clear_status(); if ssr.avf() { let sasr = self.info.regs().sasr().read(); let addr = sasr.raddr(); Ok(Event::AddressValid(addr)) } else if ssr.taf() { Ok(Event::TransmitAck) } else if ssr.rsf() { let sasr = self.info.regs().sasr().read(); let addr = sasr.raddr(); Ok(Event::RepeatedStart(addr)) } else if ssr.sdf() { let sasr = self.info.regs().sasr().read(); let addr = sasr.raddr(); Ok(Event::Stop(addr)) } else if ssr.bef() { Err(IOError::BitError) } else if ssr.fef() { Err(IOError::FifoError) } else if ssr.gcf() { Ok(Event::GeneralCall) } else if ssr.sarf() { Ok(Event::SmbusAlert) } else { Err(IOError::Other) } } // Public API: Blocking /// Block waiting for new events. /// /// This function blocks the caller until a new I2C event is received. It returns the /// type of request made by the I2C controller. /// /// # Returns /// /// - `Ok(Request)` on success. /// - `Err(IOError)` if an error occurs. pub fn blocking_listen(&mut self) -> Result { self.clear_status(); // Wait for Address Valid loop { let ssr = self.info.regs().ssr().read(); let avr = ssr.avf(); let sarf = ssr.sarf(); let gcf = ssr.gcf(); let sdf = ssr.sdf(); if avr || sarf || gcf || sdf { break; } } let event = self.status()?; match event { Event::SmbusAlert => Ok(Request::SmbusAlert), Event::GeneralCall => Ok(Request::GeneralCall), Event::Stop(addr) => Ok(Request::Stop(addr >> 1)), Event::RepeatedStart(addr) | Event::AddressValid(addr) => { if addr & 1 != 0 { Ok(Request::Read(addr >> 1)) } else { Ok(Request::Write(addr >> 1)) } } _ => Err(IOError::Other), } } /// Transmit data to the I2C controller. /// /// This function sends the contents of the provided buffer to the I2C controller. It /// blocks until the data is transmitted or an error occurs. /// /// # Parameters /// /// - `buf`: The buffer containing the data to transmit. /// /// # Returns /// /// - `Ok(usize)` with the number of bytes transmitted. /// - `Err(IOError)` if an error occurs. pub fn blocking_respond_to_read(&mut self, buf: &[u8]) -> Result { let mut count = 0; self.clear_status(); for byte in buf.iter() { // Wait until we can send data let ssr = loop { let ssr = self.info.regs().ssr().read(); let tdf = ssr.tdf(); let sdf = ssr.sdf(); let rsf = ssr.rsf(); if tdf || sdf || rsf { break ssr; } }; // If we see a STOP or REPEATED START, break out if ssr.sdf() || ssr.rsf() { #[cfg(feature = "defmt")] defmt::trace!("Early stop of Target Send routine. STOP or Repeated-start received"); break; } else { self.info.regs().stdr().write(|w| w.set_data(*byte)); count += 1; } } Ok(count) } /// Receive data from the I2C controller. /// /// This function receives data from the I2C controller into the provided buffer. It /// blocks until the buffer is filled or an error occurs. /// /// # Parameters /// /// - `buf`: The buffer to store the received data. /// /// # Returns /// /// - `Ok(usize)` with the number of bytes received. /// - `Err(IOError)` if an error occurs. pub fn blocking_respond_to_write(&mut self, buf: &mut [u8]) -> Result { let mut count = 0; self.clear_status(); for byte in buf.iter_mut() { // Wait until we have data to read let ssr = loop { let ssr = self.info.regs().ssr().read(); let rdf = ssr.rdf(); let sdf = ssr.sdf(); let rsf = ssr.rsf(); if rdf || sdf || rsf { break ssr; } }; // If we see a STOP or REPEATED START, break out if ssr.sdf() || ssr.rsf() { #[cfg(feature = "defmt")] defmt::trace!("Early stop of Target Receive routine. STOP or Repeated-start received"); break; } else { *byte = self.info.regs().srdr().read().data(); count += 1; } } Ok(count) } } impl<'d> I2c<'d, Blocking> { /// Create a new blocking instance of the I2C Target bus driver. /// /// This function initializes the I2C target driver in blocking mode. It configures the /// I2C peripheral, sets up the clock, and prepares the pins for operation. Any external /// pin will be placed into the Disabled state upon `Drop`. /// /// # Parameters /// /// - `peri`: The I2C peripheral instance. /// - `scl`: The SCL pin. /// - `sda`: The SDA pin. /// - `config`: The configuration for the I2C target. /// /// # Returns /// /// - `Ok(Self)` on success. /// - `Err(SetupError)` if initialization fails. pub fn new_blocking( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, ) -> Result { Self::new_inner(peri, scl, sda, config, Blocking) } } impl<'d> I2c<'d, Async> { /// Create a new asynchronous instance of the I2C Target bus driver. /// /// This function initializes the I2C target driver in asynchronous mode. It configures the /// I2C peripheral, sets up the clock, and prepares the pins for operation. Any external /// pin will be placed into the Disabled state upon `Drop`. /// /// # Parameters /// /// - `peri`: The I2C peripheral instance. /// - `scl`: The SCL pin. /// - `sda`: The SDA pin. /// - `_irq`: The interrupt binding for the I2C peripheral. /// - `config`: The configuration for the I2C target. /// /// # Returns /// /// - `Ok(Self)` on success. /// - `Err(SetupError)` if initialization fails. pub fn new_async( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable() }; Self::new_inner(peri, scl, sda, config, Async) } } impl<'d> I2c<'d, Dma<'d>> { /// Create a new asynchronous instance of the I2C Target bus driver with DMA support. /// /// This function initializes the I2C target driver in asynchronous mode with DMA support. /// It configures the I2C peripheral, sets up the clock, and prepares the pins for operation. /// Any external pin will be placed into the Disabled state upon `Drop`, and the DMA channels /// are also disabled. /// /// # Parameters /// /// - `peri`: The I2C peripheral instance. /// - `scl`: The SCL pin. /// - `sda`: The SDA pin. /// - `tx_dma`: The DMA channel for transmitting data. /// - `rx_dma`: The DMA channel for receiving data. /// - `_irq`: The interrupt binding for the I2C peripheral. /// - `config`: The configuration for the I2C target. /// /// # Returns /// /// - `Ok(Self)` on success. /// - `Err(SetupError)` if initialization fails. pub fn new_async_with_dma( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, tx_dma: Peri<'d, impl Channel>, rx_dma: Peri<'d, impl Channel>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable() }; // enable this channel's interrupt let tx_dma = DmaChannel::new(tx_dma); let rx_dma = DmaChannel::new(rx_dma); tx_dma.enable_interrupt(); rx_dma.enable_interrupt(); Self::new_inner( peri, scl, sda, config, Dma { tx_dma, rx_dma, tx_request: T::TX_DMA_REQUEST, rx_request: T::RX_DMA_REQUEST, }, ) } async fn read_dma_chunk(&mut self, data: &mut [u8]) -> Result { let peri_addr = self.info.regs().srdr().as_ptr() as *const u8; self.clear_status(); unsafe { // Clean up channel state self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.rx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.rx_dma.set_request_source(self.mode.rx_request); // Configure TCD for memory-to-peripheral transfer self.mode .rx_dma .setup_read_from_peripheral(peri_addr, data, false, TransferOptions::COMPLETE_INTERRUPT)?; // Enable I2C RX DMA request self.info.regs().sder().modify(|w| w.set_rdde(true)); // Enable DMA channel request self.mode.rx_dma.enable_request(); } // Wait until STOP or REPEATED START self.info .wait_cell() .wait_for(|| { self.info.regs().sier().write(|w| { w.set_feie(true); w.set_beie(true); w.set_sdie(true); w.set_rsie(true); }); let ssr = self.info.regs().ssr().read(); ssr.fef() || ssr.bef() || ssr.sdf() || ssr.rsf() }) .await .map_err(|_| IOError::Other)?; // Cleanup self.info.regs().sder().modify(|w| w.set_rdde(false)); unsafe { self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); } // Ensure all writes by DMA are visible to the CPU // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Acquire); let ssr = self.info.regs().ssr().read(); if ssr.fef() { Err(IOError::FifoError) } else if ssr.bef() { Err(IOError::BitError) } else if ssr.sdf() || ssr.rsf() { Ok(self.mode.rx_dma.transferred_bytes()) } else { Err(IOError::Other) } } async fn write_dma_chunk(&mut self, data: &[u8]) -> Result { let peri_addr = self.info.regs().stdr().as_ptr() as *mut u8; self.clear_status(); unsafe { // Clean up channel state self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.tx_dma.set_request_source(self.mode.tx_request); // Configure TCD for memory-to-peripheral transfer self.mode .tx_dma .setup_write_to_peripheral(data, peri_addr, false, TransferOptions::NO_INTERRUPTS)?; // Ensure all writes by DMA are visible to the CPU // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Release); // Enable I2C TX DMA request self.info.regs().sder().modify(|w| w.set_tdde(true)); // Enable DMA channel request self.mode.tx_dma.enable_request(); } // Wait until STOP or REPEATED START self.info .wait_cell() .wait_for(|| { self.info.regs().sier().write(|w| { w.set_feie(true); w.set_beie(true); w.set_sdie(true); w.set_rsie(true); }); let ssr = self.info.regs().ssr().read(); ssr.fef() || ssr.bef() || ssr.sdf() || ssr.rsf() }) .await .map_err(|_| IOError::Other)?; // Cleanup self.info.regs().sder().modify(|w| w.set_tdde(false)); unsafe { self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } let ssr = self.info.regs().ssr().read(); if ssr.fef() { Err(IOError::FifoError) } else if ssr.bef() { Err(IOError::BitError) } else if ssr.sdf() || ssr.rsf() { Ok(self.mode.tx_dma.transferred_bytes()) } else { Err(IOError::Other) } } } #[allow(private_bounds)] impl<'d, M: AsyncMode> I2c<'d, M> where Self: AsyncEngine, { fn enable_ints(&self) { self.info.regs().sier().write(|w| { w.set_sarie(self.smbus_alert.clone().into()); w.set_gcie(self.general_call.clone().into()); w.set_am1ie(true); w.set_am0ie(true); w.set_feie(true); w.set_beie(true); w.set_sdie(true); w.set_rsie(true); w.set_taie(true); w.set_avie(true); w.set_rdie(true); w.set_tdie(true); }); } // Public API: Async /// Asynchronously wait for new events. /// /// This function waits asynchronously for a new I2C event and returns the type of /// request made by the I2C controller. /// /// # Returns /// /// - `Ok(Request)` on success. /// - `Err(IOError)` if an error occurs. pub async fn async_listen(&mut self) -> Result { self.clear_status(); self.info .wait_cell() .wait_for(|| { self.enable_ints(); let status = self.info.regs().ssr().read(); status.avf() || status.sarf() || status.gcf() || status.sdf() }) .await .map_err(|_| IOError::Other)?; let event = self.status()?; match event { Event::SmbusAlert => Ok(Request::SmbusAlert), Event::GeneralCall => Ok(Request::GeneralCall), Event::Stop(addr) => Ok(Request::Stop(addr >> 1)), Event::RepeatedStart(addr) | Event::AddressValid(addr) => { if addr & 1 != 0 { Ok(Request::Read(addr >> 1)) } else { Ok(Request::Write(addr >> 1)) } } _ => Err(IOError::Other), } } /// Asynchronously transmit data to the I2C controller. /// /// This function sends the contents of the provided buffer to the I2C controller /// asynchronously. /// /// # Parameters /// /// - `buf`: The buffer containing the data to transmit. /// /// # Returns /// /// - `Ok(usize)` with the number of bytes transmitted. /// - `Err(IOError)` if an error occurs. pub fn async_respond_to_read<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a { ::async_respond_to_read_internal(self, buf) } /// Asynchronously receive data from the I2C controller. /// /// This function receives data from the I2C controller into the provided buffer /// asynchronously. /// /// # Parameters /// /// - `buf`: The buffer to store the received data. /// /// # Returns /// /// - `Ok(usize)` with the number of bytes received. /// - `Err(IOError)` if an error occurs. pub fn async_respond_to_write<'a>( &'a mut self, buf: &'a mut [u8], ) -> impl Future> + 'a { ::async_respond_to_write_internal(self, buf) } } trait AsyncEngine { fn async_respond_to_read_internal<'a>( &'a mut self, buf: &'a [u8], ) -> impl Future> + 'a; fn async_respond_to_write_internal<'a>( &'a mut self, buf: &'a mut [u8], ) -> impl Future> + 'a; } impl<'d> AsyncEngine for I2c<'d, Async> { async fn async_respond_to_read_internal(&mut self, buf: &[u8]) -> Result { let mut count = 0; self.clear_status(); for byte in buf.iter() { // Wait until we can send data self.info .wait_cell() .wait_for(|| { self.enable_ints(); let ssr = self.info.regs().ssr().read(); ssr.tdf() || ssr.sdf() || ssr.rsf() }) .await .map_err(|_| IOError::Other)?; // If we see a STOP or REPEATED START, break out let ssr = self.info.regs().ssr().read(); if ssr.sdf() || ssr.rsf() { #[cfg(feature = "defmt")] defmt::trace!("Early stop of Target Send routine. STOP or Repeated-start received"); self.reset_fifos(); break; } else { self.info.regs().stdr().write(|w| w.set_data(*byte)); count += 1; } } Ok(count) } async fn async_respond_to_write_internal(&mut self, buf: &mut [u8]) -> Result { let mut count = 0; self.clear_status(); for byte in buf.iter_mut() { self.info .wait_cell() .wait_for(|| { self.enable_ints(); let ssr = self.info.regs().ssr().read(); ssr.rdf() || ssr.sdf() || ssr.rsf() }) .await .map_err(|_| IOError::Other)?; // If we see a STOP or REPEATED START, break out let ssr = self.info.regs().ssr().read(); if ssr.sdf() || ssr.rsf() { #[cfg(feature = "defmt")] defmt::trace!("Early stop of Target Receive routine. STOP or Repeated-start received"); self.reset_fifos(); break; } else { *byte = self.info.regs().srdr().read().data(); count += 1; } } Ok(count) } } impl<'d> AsyncEngine for I2c<'d, Dma<'d>> { async fn async_respond_to_read_internal(&mut self, buf: &[u8]) -> Result { let mut count = 0; self.clear_status(); // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.info.regs().sder().modify(|w| w.set_tdde(false)); }); for chunk in buf.chunks(DMA_MAX_TRANSFER_SIZE) { count += self.write_dma_chunk(chunk).await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(count) } async fn async_respond_to_write_internal<'a>(&'a mut self, buf: &'a mut [u8]) -> Result { let mut count = 0; self.clear_status(); // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.info.regs().sder().modify(|w| w.set_rdde(false)); }); for chunk in buf.chunks_mut(DMA_MAX_TRANSFER_SIZE) { count += self.read_dma_chunk(chunk).await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(count) } } impl<'d, M: Mode> Drop for I2c<'d, M> { fn drop(&mut self) { self._scl.set_as_disabled(); self._sda.set_as_disabled(); } } ================================================ FILE: embassy-mcxa/src/i3c/controller.rs ================================================ //! I3C Controller driver. use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; use nxp_pac::i3c::vals::{MdmactrlDmafb, MdmactrlDmatb}; use super::{Async, AsyncMode, Blocking, Dma, Info, Instance, InterruptHandler, Mode, SclPin, SdaPin}; use crate::clocks::periph_helpers::{Div4, I3cClockSel, I3cConfig}; use crate::clocks::{ClockError, PoweredClock, WakeGuard, enable_and_reset}; use crate::dma::{Channel, DMA_MAX_TRANSFER_SIZE, DmaChannel, TransferOptions}; use crate::gpio::{AnyPin, SealedPin}; pub use crate::i2c::controller::Speed; use crate::interrupt::typelevel; use crate::interrupt::typelevel::Interrupt; use crate::pac::i3c::vals::{ Disto, Hkeep, Ibiresp, MctrlDir as I3cDir, MdatactrlRxtrig, MdatactrlTxtrig, Mstena, Request, State, Type, }; const MAX_CHUNK_SIZE: usize = 255; /// Setup Errors #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum SetupError { /// Clock configuration error. ClockSetup(ClockError), /// User provided an invalid configuration InvalidConfiguration, /// Other internal errors or unexpected state. Other, } /// I/O Errors #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum IOError { /// Underrun error Underrun, /// Not Acknowledge error Nack, /// Write abort error WriteAbort, /// Terminate error Terminate, /// High data rate parity flag HighDataRateParity, /// High data rate CRC error HighDataRateCrc, /// Overread error Overread, /// Overwrite error Overwrite, /// Message error Message, /// Invalid request error InvalidRequest, /// Timeout error Timeout, /// Address out of range. AddressOutOfRange(u8), /// Invalid write buffer length. InvalidWriteBufferLength, /// Invalid read buffer length. InvalidReadBufferLength, /// Other internal errors or unexpected state. Other, } impl From for IOError { fn from(_value: crate::dma::InvalidParameters) -> Self { Self::Other } } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum SendStop { #[default] No, Yes, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[allow(dead_code)] pub enum BusType { /// I3C SDR #[default] I3cSdr, /// Legacy I2C I2c, /// I3C DDR I3cDdr, } impl From for Type { fn from(value: BusType) -> Self { match value { BusType::I3cSdr => Self::I3C, BusType::I2c => Self::I2C, BusType::I3cDdr => Self::DDR, } } } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum Dir { #[default] Write, Read, } impl From

for I3cDir { fn from(value: Dir) -> Self { match value { Dir::Write => Self::DIRWRITE, Dir::Read => Self::DIRREAD, } } } /// I3C controller configuration #[non_exhaustive] pub struct Config { /// I3C push-pull bus frequency in Hz. pub push_pull_freq: u32, /// I3C open-drain frequency in Hz. pub open_drain_freq: u32, /// I2C bus speed pub i2c_speed: Speed, /// Clock configuration pub clock_config: ClockConfig, } impl Default for Config { fn default() -> Self { Self { push_pull_freq: 1_500_000, open_drain_freq: 750_000, i2c_speed: Speed::Fast, clock_config: ClockConfig::default(), } } } /// I3C controller clock configuration #[derive(Clone)] #[non_exhaustive] pub struct ClockConfig { /// Powered clock configuration pub power: PoweredClock, /// I3C clock source pub source: I3cClockSel, /// I3C pre-divider pub div: Div4, } impl Default for ClockConfig { fn default() -> Self { Self { power: PoweredClock::NormalEnabledDeepSleepDisabled, source: I3cClockSel::FroLfDiv, div: const { Div4::no_div() }, } } } fn calculate_error(cur_freq: u32, desired_freq: u32) -> u32 { let delta = cur_freq.abs_diff(desired_freq); delta * 100 / desired_freq } /// I3C controller driver. pub struct I3c<'d, M: Mode> { info: &'static Info, _scl: Peri<'d, AnyPin>, _sda: Peri<'d, AnyPin>, mode: M, fclk: u32, _wg: Option, } impl<'d, M: Mode> I3c<'d, M> { fn new_inner( _peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, mode: M, ) -> Result { let ClockConfig { power, source, div } = config.clock_config; // Enable clocks let conf = I3cConfig { power, source, div }; let parts = unsafe { enable_and_reset::(&conf).map_err(SetupError::ClockSetup)? }; scl.mux(); sda.mux(); let _scl = scl.into(); let _sda = sda.into(); let inst = Self { info: T::info(), _scl, _sda, mode, fclk: parts.freq, _wg: parts.wake_guard, }; inst.set_configuration(&config)?; Ok(inst) } fn set_configuration(&self, config: &Config) -> Result<(), SetupError> { self.clear_flags(); self.info.regs().mdatactrl().modify(|w| { w.set_flushtb(true); w.set_flushfb(true); w.set_unlock(true); w.set_txtrig(MdatactrlTxtrig::FULL_OR_LESS); w.set_rxtrig(MdatactrlRxtrig::NOT_EMPTY); }); let (ppbaud, odbaud, i2cbaud) = self.calculate_baud_rate_params(config)?; self.info.regs().mconfig().write(|w| { w.set_ppbaud(ppbaud as u8); w.set_odbaud(odbaud as u8); w.set_i2cbaud(i2cbaud as u8); w.set_mstena(Mstena::MASTER_ON); w.set_disto(Disto::ENABLE); w.set_hkeep(Hkeep::NONE); w.set_odstop(false); w.set_odhpp(true); }); Ok(()) } // REVISIT: not very readable fn calculate_baud_rate_params(&self, config: &Config) -> Result<(u32, u32, u32), SetupError> { const NSEC_PER_SEC: u32 = 1_000_000_000; let fclk = self.fclk; let target_pp_hz = config.push_pull_freq; if target_pp_hz == 0 { return Err(SetupError::InvalidConfiguration); } let max_pp_hz = target_pp_hz + target_pp_hz / 10; let target_od_hz = config.open_drain_freq; let max_od_hz = target_od_hz + target_od_hz / 10; let target_i2c_hz = >::into(config.i2c_speed); /* ------------------------------------------------------------- * 1) Push‑Pull baud (PPBAUD) * Generated from fclk / 2 * ------------------------------------------------------------- */ let mut pp_src_hz = fclk / 2; let mut pp_div = (pp_src_hz / target_pp_hz).max(1); if pp_src_hz / pp_div > max_pp_hz { pp_div += 1; } let pp_baud = pp_div - 1; pp_src_hz /= pp_div; let pp_low_ns = NSEC_PER_SEC / (2 * pp_src_hz); /* ------------------------------------------------------------- * 2) Open‑Drain baud (ODBAUD) * Depends on ODHPP mode * ------------------------------------------------------------- */ let odhpp_enabled = self.info.regs().mconfig().read().odhpp(); let (od_baud, _od_src_hz) = if odhpp_enabled { // OD rate derived from 2×PP clock let mut div = ((2 * pp_src_hz) / target_od_hz).max(2); if (2 * pp_src_hz) / div > max_od_hz { div += 1; } (div - 2, (2 * pp_src_hz) / div) } else { // OD rate derived directly let mut div = (pp_src_hz / target_od_hz).max(1); if pp_src_hz / div > max_od_hz { div += 1; } (div - 1, pp_src_hz / div) }; let od_low_ns = (od_baud + 1) * pp_low_ns; /* ------------------------------------------------------------- * 3) I²C baud selection * Choose even/odd divider with lowest error * ------------------------------------------------------------- */ let even_div = ((fclk / target_i2c_hz) / (2 * (pp_baud + 1) * (od_baud + 1))).max(1); let even_rate = NSEC_PER_SEC / (2 * even_div * od_low_ns); let even_error = calculate_error(even_rate, target_i2c_hz); let odd_div = (((fclk / target_i2c_hz) / ((pp_baud + 1) * (od_baud + 1) - 1)) / 2).max(1); let odd_rate = NSEC_PER_SEC / ((2 * odd_div + 1) * od_low_ns); let odd_error = calculate_error(odd_rate, target_i2c_hz); let i2c_baud = if even_error < 10 || odd_error < 10 { if even_error < odd_error { (even_div - 1) * 2 } else { (odd_div - 1) * 2 + 1 } } else if pp_src_hz / even_div < target_i2c_hz { (even_div - 1) * 2 } else { even_div * 2 }; Ok((pp_baud, od_baud, i2c_baud)) } fn blocking_remediation(&self, bus_type: BusType) { // if the FIFO is not empty, drop its contents. if self.info.regs().mdatactrl().read().txcount() != 0 { self.info.regs().mdatactrl().modify(|w| { w.set_flushtb(true); w.set_flushfb(true); }); } // send a stop command let _ = self.blocking_stop(bus_type); } fn clear_flags(&self) { self.info.regs().mstatus().write(|w| { w.set_slvstart(true); w.set_mctrldone(true); w.set_complete(true); w.set_ibiwon(true); w.set_nowmaster(true); }); } fn blocking_wait_for_ctrldone(&self) { while !self.info.regs().mstatus().read().mctrldone() {} } fn blocking_wait_for_complete(&self) { while !self.info.regs().mstatus().read().complete() {} } fn blocking_wait_for_tx_fifo(&self) { while self.info.regs().mdatactrl().read().txfull() {} } fn blocking_wait_for_rx_fifo(&self) { while self.info.regs().mdatactrl().read().rxempty() {} } fn status(&self) -> Result<(), IOError> { if self.info.regs().mstatus().read().errwarn() { let merrwarn = self.info.regs().merrwarn().read(); if merrwarn.urun() { Err(IOError::Underrun) } else if merrwarn.nack() { Err(IOError::Nack) } else if merrwarn.wrabt() { Err(IOError::WriteAbort) } else if merrwarn.term() { Err(IOError::Terminate) } else if merrwarn.hpar() { Err(IOError::HighDataRateParity) } else if merrwarn.hcrc() { Err(IOError::HighDataRateCrc) } else if merrwarn.oread() { Err(IOError::Overread) } else if merrwarn.owrite() { Err(IOError::Overwrite) } else if merrwarn.msgerr() { Err(IOError::Message) } else if merrwarn.invreq() { Err(IOError::InvalidRequest) } else if merrwarn.timeout() { Err(IOError::Timeout) } else { // should never happen Err(IOError::Other) } } else { Ok(()) } } /// Prepares an appropriate Start condition on bus by issuing a /// `Start` request together with the device address, bus type /// (i3c sdr, i3c ddr, or i2c), and R/w bit. fn blocking_start(&self, address: u8, bus_type: BusType, dir: Dir, len: u8) -> Result<(), IOError> { self.clear_flags(); self.info.regs().mctrl().write(|w| { w.set_addr(address); w.set_rdterm(len); w.set_type_(bus_type.into()); w.set_request(Request::EMITSTARTADDR); w.set_dir(dir.into()); w.set_ibiresp(Ibiresp::ACK); }); self.blocking_wait_for_ctrldone(); self.status() } /// Prepares a Stop condition on the bus. /// /// Analogous to `start`, this blocks waiting for space in the /// FIFO to become available, then sends the command and blocks /// waiting for the FIFO to become empty ensuring the command was /// sent. fn blocking_stop(&self, bus_type: BusType) -> Result<(), IOError> { if self.info.regs().mstatus().read().state() != State::NORMACT { Err(IOError::InvalidRequest) } else { // NOTE: Section 41.3.2.1 states that "when sending STOP // in I2C mode, MCONFIG[ODSTOP] and MCTRL[TYPE] must be // 1". self.info .regs() .mconfig() .modify(|w| w.set_odstop(bus_type == BusType::I2c)); self.info.regs().mctrl().write(|w| { w.set_request(Request::EMITSTOP); w.set_type_(bus_type.into()) }); self.blocking_wait_for_ctrldone(); self.status() } } fn blocking_read_internal( &self, address: u8, read: &mut [u8], bus_type: BusType, send_stop: SendStop, ) -> Result<(), IOError> { if read.is_empty() { return Err(IOError::InvalidReadBufferLength); } for chunk in read.chunks_mut(MAX_CHUNK_SIZE) { if let Err(e) = self.blocking_start(address, bus_type, Dir::Read, chunk.len() as u8) { self.blocking_remediation(bus_type); return Err(e); }; for byte in chunk.iter_mut() { self.blocking_wait_for_rx_fifo(); *byte = self.info.regs().mrdatab().read().value(); } } if send_stop == SendStop::Yes { self.blocking_stop(bus_type)?; } Ok(()) } fn blocking_write_internal( &self, address: u8, write: &[u8], bus_type: BusType, send_stop: SendStop, ) -> Result<(), IOError> { if let Err(e) = self.blocking_start(address, bus_type, Dir::Write, 0) { self.blocking_remediation(bus_type); return Err(e); }; // Usually, embassy HALs error out with an empty write, // however empty writes are useful for writing I2C scanning // logic through write probing. That is, we send a start with // R/w bit cleared, but instead of writing any data, just send // the stop onto the bus. This has the effect of checking if // the resulting address got an ACK but causing no // side-effects to the device on the other end. // // Because of this, we are not going to error out in case of // empty writes. if write.is_empty() { #[cfg(feature = "defmt")] defmt::trace!("Empty write, write probing?"); if send_stop == SendStop::Yes { self.blocking_stop(bus_type)?; } return Ok(()); } let Some((last, rest)) = write.split_last() else { return Err(IOError::InvalidWriteBufferLength); }; for byte in rest { // Wait until we have space in the TX FIFO. self.blocking_wait_for_tx_fifo(); self.info.regs().mwdatab().write(|w| w.set_value(*byte)); } self.blocking_wait_for_tx_fifo(); self.info.regs().mwdatabe().write(|w| w.set_value(*last)); self.blocking_wait_for_complete(); if send_stop == SendStop::Yes { self.blocking_stop(bus_type)?; } Ok(()) } // Public API: Blocking /// Read from address into buffer blocking caller until done. pub fn blocking_read(&mut self, address: u8, read: &mut [u8], bus_type: BusType) -> Result<(), IOError> { self.blocking_read_internal(address, read, bus_type, SendStop::Yes) } /// Write to address from buffer blocking caller until done. pub fn blocking_write(&mut self, address: u8, write: &[u8], bus_type: BusType) -> Result<(), IOError> { self.blocking_write_internal(address, write, bus_type, SendStop::Yes) } /// Write to address from bytes and read from address into buffer blocking caller until done. pub fn blocking_write_read( &mut self, address: u8, write: &[u8], read: &mut [u8], bus_type: BusType, ) -> Result<(), IOError> { self.blocking_write_internal(address, write, bus_type, SendStop::No)?; self.blocking_read_internal(address, read, bus_type, SendStop::Yes) } } impl<'d> I3c<'d, Blocking> { /// Create a new blocking instance of the I3C controller bus driver. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, ) -> Result { Self::new_inner(peri, scl, sda, config, Blocking) } } trait AsyncEngine { fn async_read_internal<'a>( &'a self, address: u8, read: &'a mut [u8], bus_type: BusType, send_stop: SendStop, ) -> impl Future> + 'a; fn async_write_internal<'a>( &'a self, address: u8, write: &'a [u8], bus_type: BusType, send_stop: SendStop, ) -> impl Future> + 'a; } impl<'d> I3c<'d, Async> { /// Create a new asynchronous instance of the I3C controller bus driver. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_async( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, _irq: impl typelevel::Binding> + 'd, config: Config, ) -> Result { let inst = Self::new_inner(peri, scl, sda, config, Async); T::Interrupt::unpend(); unsafe { T::Interrupt::enable(); } inst } } impl<'d> AsyncEngine for I3c<'d, Async> { async fn async_read_internal( &self, address: u8, read: &mut [u8], bus_type: BusType, send_stop: SendStop, ) -> Result<(), IOError> { if read.is_empty() { return Err(IOError::InvalidReadBufferLength); } // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.blocking_remediation(bus_type); }); for chunk in read.chunks_mut(MAX_CHUNK_SIZE) { self.async_start(address, bus_type, Dir::Read, chunk.len() as u8) .await?; for byte in chunk.iter_mut() { self.async_wait_for_rx_fifo().await?; *byte = self.info.regs().mrdatab().read().value(); } } if send_stop == SendStop::Yes { self.async_stop(bus_type).await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } async fn async_write_internal( &self, address: u8, write: &[u8], bus_type: BusType, send_stop: SendStop, ) -> Result<(), IOError> { // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.blocking_remediation(bus_type); }); self.async_start(address, bus_type, Dir::Write, 0).await?; // Usually, embassy HALs error out with an empty write, // however empty writes are useful for writing I2C scanning // logic through write probing. That is, we send a start with // R/w bit cleared, but instead of writing any data, just send // the stop onto the bus. This has the effect of checking if // the resulting address got an ACK but causing no // side-effects to the device on the other end. // // Because of this, we are not going to error out in case of // empty writes. if write.is_empty() { #[cfg(feature = "defmt")] defmt::trace!("Empty write, write probing?"); if send_stop == SendStop::Yes { self.async_stop(bus_type).await?; } return Ok(()); } let Some((last, rest)) = write.split_last() else { return Err(IOError::InvalidWriteBufferLength); }; for byte in rest { self.async_wait_for_tx_fifo().await?; self.info.regs().mwdatab().write(|w| w.set_value(*byte)); } // Wait until we have space in the TX FIFO. self.async_wait_for_tx_fifo().await?; self.info.regs().mwdatabe().write(|w| w.set_value(*last)); // Wait for complete self.async_wait_for_complete().await?; if send_stop == SendStop::Yes { self.async_stop(bus_type).await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } } impl<'d> I3c<'d, Dma<'d>> { /// Create a new async instance of the I3C Controller bus driver /// with DMA support. /// /// Any external pin will be placed into Disabled state upon Drop, /// additionally, the DMA channel is disabled. pub fn new_async_with_dma( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, tx_dma: Peri<'d, impl Channel>, rx_dma: Peri<'d, impl Channel>, _irq: impl typelevel::Binding> + 'd, config: Config, ) -> Result { T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable() }; // enable this channel's interrupt let tx_dma = DmaChannel::new(tx_dma); let rx_dma = DmaChannel::new(rx_dma); tx_dma.enable_interrupt(); rx_dma.enable_interrupt(); Self::new_inner( peri, scl, sda, config, Dma { tx_dma, rx_dma, tx_request: T::TX_DMA_REQUEST, rx_request: T::RX_DMA_REQUEST, }, ) } } impl<'d> AsyncEngine for I3c<'d, Dma<'d>> { async fn async_read_internal( &self, address: u8, read: &mut [u8], bus_type: BusType, send_stop: SendStop, ) -> Result<(), IOError> { if read.is_empty() { return Err(IOError::InvalidReadBufferLength); } // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.blocking_remediation(bus_type); self.info .regs() .mdmactrl() .modify(|w| w.set_dmafb(MdmactrlDmafb::NOT_USED)); }); for chunk in read.chunks_mut(MAX_CHUNK_SIZE) { self.async_start(address, bus_type, Dir::Read, chunk.len() as u8) .await?; let peri_addr = self.info.regs().mrdatab().as_ptr() as *const u8; unsafe { // Clean up channel state self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.rx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.rx_dma.set_request_source(self.mode.rx_request); // Configure TCD for peripheral-to-memory transfer self.mode.rx_dma.setup_read_from_peripheral( peri_addr, chunk, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Enable I3C RX DMA request self.info .regs() .mdmactrl() .modify(|w| w.set_dmafb(MdmactrlDmafb::ENABLE)); // Enable DMA channel request self.mode.rx_dma.enable_request(); } // Wait for completion asynchronously core::future::poll_fn(|cx| { self.mode.rx_dma.waker().register(cx.waker()); if self.mode.rx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Ensure DMA writes are visible to CPU cortex_m::asm::dsb(); // Cleanup self.info .regs() .mdmactrl() .modify(|w| w.set_dmafb(MdmactrlDmafb::NOT_USED)); unsafe { self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); } } if send_stop == SendStop::Yes { self.async_stop(bus_type).await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } async fn async_write_internal( &self, address: u8, write: &[u8], bus_type: BusType, send_stop: SendStop, ) -> Result<(), IOError> { // perform corrective action if the future is dropped let on_drop = OnDrop::new(|| { self.blocking_remediation(bus_type); self.info .regs() .mdmactrl() .modify(|w| w.set_dmatb(MdmactrlDmatb::NOT_USED)); }); self.async_start(address, bus_type, Dir::Write, 0).await?; // Usually, embassy HALs error out with an empty write, // however empty writes are useful for writing I2C scanning // logic through write probing. That is, we send a start with // R/w bit cleared, but instead of writing any data, just send // the stop onto the bus. This has the effect of checking if // the resulting address got an ACK but causing no // side-effects to the device on the other end. // // Because of this, we are not going to error out in case of // empty writes. if write.is_empty() { #[cfg(feature = "defmt")] defmt::trace!("Empty write, write probing?"); if send_stop == SendStop::Yes { self.async_stop(bus_type).await?; } return Ok(()); } let Some((last, rest)) = write.split_last() else { return Err(IOError::InvalidWriteBufferLength); }; for chunk in rest.chunks(DMA_MAX_TRANSFER_SIZE) { let peri_addr = self.info.regs().mwdatab().as_ptr() as *mut u8; unsafe { // Clean up channel state self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.tx_dma.set_request_source(self.mode.tx_request); // Configure TCD for memory-to-peripheral transfer self.mode.tx_dma.setup_write_to_peripheral( chunk, peri_addr, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Enable I3C TX DMA request self.info .regs() .mdmactrl() .modify(|w| w.set_dmatb(MdmactrlDmatb::ENABLE)); // Enable DMA channel request self.mode.tx_dma.enable_request(); } // Wait for completion asynchronously core::future::poll_fn(|cx| { self.mode.tx_dma.waker().register(cx.waker()); if self.mode.tx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Ensure DMA writes are visible to CPU cortex_m::asm::dsb(); // Cleanup self.info .regs() .mdmactrl() .modify(|w| w.set_dmatb(MdmactrlDmatb::NOT_USED)); unsafe { self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } } // Wait until we have space in the TX FIFO. self.async_wait_for_tx_fifo().await?; self.info.regs().mwdatabe().write(|w| w.set_value(*last)); // Wait for complete self.async_wait_for_complete().await?; if send_stop == SendStop::Yes { self.async_stop(bus_type).await?; } // defuse it if the future is not dropped on_drop.defuse(); Ok(()) } } #[allow(private_bounds)] impl<'d, M: AsyncMode> I3c<'d, M> where Self: AsyncEngine, { async fn async_wait_for_ctrldone(&self) -> Result<(), IOError> { self.info .wait_cell() .wait_for(|| { // enable control done interrupt self.info.regs().mintset().write(|w| { w.set_mctrldone(true); w.set_errwarn(true); }); // check control done status self.info.regs().mstatus().read().mctrldone() || self.info.regs().mstatus().read().errwarn() }) .await .map_err(|_| IOError::Other) } async fn async_wait_for_complete(&self) -> Result<(), IOError> { self.info .wait_cell() .wait_for(|| { // enable control done interrupt self.info.regs().mintset().write(|w| { w.set_complete(true); w.set_errwarn(true); }); // check control done status self.info.regs().mstatus().read().complete() || self.info.regs().mstatus().read().errwarn() }) .await .map_err(|_| IOError::Other) } async fn async_wait_for_tx_fifo(&self) -> Result<(), IOError> { // Wait until we have space in the TX FIFO. self.info .wait_cell() .wait_for(|| { // enable TXNOTFULL interrupt self.info.regs().mintset().write(|w| { w.set_txnotfull(true); w.set_errwarn(true); }); // if the TX FIFO isn't full, we can write bytes self.info.regs().mstatus().read().txnotfull() || self.info.regs().mstatus().read().errwarn() }) .await .map_err(|_| IOError::Overwrite) } async fn async_wait_for_rx_fifo(&self) -> Result<(), IOError> { self.info .wait_cell() .wait_for(|| { // enable RXPEND interrupt self.info.regs().mintset().write(|w| { w.set_rxpend(true); w.set_errwarn(true); }); // if the rx FIFO is pending, we need to read bytes self.info.regs().mstatus().read().rxpend() || self.info.regs().mstatus().read().errwarn() }) .await .map_err(|_| IOError::Overread) } /// Prepares an appropriate Start condition on bus by issuing a /// `Start` request together with the device address, bus type /// (i3c sdr, i3c ddr, or i2c), and R/w bit. async fn async_start(&self, address: u8, bus_type: BusType, dir: Dir, len: u8) -> Result<(), IOError> { self.clear_flags(); self.info.regs().mctrl().write(|w| { w.set_addr(address); w.set_rdterm(len); w.set_type_(bus_type.into()); w.set_request(Request::EMITSTARTADDR); w.set_dir(dir.into()); w.set_ibiresp(Ibiresp::ACK); }); self.async_wait_for_ctrldone().await?; self.status() } /// Prepares a Stop condition on the bus. /// /// Analogous to `start`, this blocks waiting for space in the /// FIFO to become available, then sends the command and blocks /// waiting for the FIFO to become empty ensuring the command was /// sent. async fn async_stop(&self, bus_type: BusType) -> Result<(), IOError> { if self.info.regs().mstatus().read().state() != State::NORMACT { Err(IOError::InvalidRequest) } else { // NOTE: Section 41.3.2.1 states that "when sending STOP // in I2C mode, MCONFIG[ODSTOP] and MCTRL[TYPE] must be // 1". self.info .regs() .mconfig() .modify(|w| w.set_odstop(bus_type == BusType::I2c)); self.info.regs().mctrl().write(|w| { w.set_request(Request::EMITSTOP); w.set_type_(bus_type.into()); }); self.async_wait_for_ctrldone().await?; self.status() } } // Public API: Async /// Read from address into buffer asynchronously. pub fn async_read<'a>( &'a mut self, address: u8, read: &'a mut [u8], bus_type: BusType, ) -> impl Future> + 'a { ::async_read_internal(self, address, read, bus_type, SendStop::Yes) } /// Write to address from buffer asynchronously. pub fn async_write<'a>( &'a mut self, address: u8, write: &'a [u8], bus_type: BusType, ) -> impl Future> + 'a { ::async_write_internal(self, address, write, bus_type, SendStop::Yes) } /// Write to address from bytes and then read from address into buffer asynchronously. pub async fn async_write_read<'a>( &'a mut self, address: u8, write: &'a [u8], read: &'a mut [u8], bus_type: BusType, ) -> Result<(), IOError> { ::async_write_internal(self, address, write, bus_type, SendStop::No).await?; ::async_read_internal(self, address, read, bus_type, SendStop::Yes).await } } impl<'d, M: Mode> Drop for I3c<'d, M> { fn drop(&mut self) { self._scl.set_as_disabled(); self._sda.set_as_disabled(); } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Read for I3c<'d, M> { type Error = IOError; fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { self.blocking_read(address, buffer, BusType::I2c) } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Write for I3c<'d, M> { type Error = IOError; fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { self.blocking_write(address, bytes, BusType::I2c) } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I3c<'d, M> { type Error = IOError; fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { self.blocking_write_read(address, bytes, buffer, BusType::I2c) } } impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I3c<'d, M> { type Error = IOError; fn exec( &mut self, address: u8, operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>], ) -> Result<(), Self::Error> { let Some((last, rest)) = operations.split_last_mut() else { return Ok(()); }; for op in rest { match op { embedded_hal_02::blocking::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, BusType::I2c, SendStop::No)? } embedded_hal_02::blocking::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, BusType::I2c, SendStop::No)? } } } match last { embedded_hal_02::blocking::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, BusType::I2c, SendStop::Yes) } embedded_hal_02::blocking::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, BusType::I2c, SendStop::Yes) } } } } impl embedded_hal_1::i2c::Error for IOError { fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { match *self { Self::Nack => { embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown) } _ => embedded_hal_1::i2c::ErrorKind::Other, } } } impl<'d, M: Mode> embedded_hal_1::i2c::ErrorType for I3c<'d, M> { type Error = IOError; } impl<'d, M: Mode> embedded_hal_1::i2c::I2c for I3c<'d, M> { fn transaction( &mut self, address: u8, operations: &mut [embedded_hal_1::i2c::Operation<'_>], ) -> Result<(), Self::Error> { let Some((last, rest)) = operations.split_last_mut() else { return Ok(()); }; for op in rest { match op { embedded_hal_1::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, BusType::I2c, SendStop::No)? } embedded_hal_1::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, BusType::I2c, SendStop::No)? } } } match last { embedded_hal_1::i2c::Operation::Read(buf) => { self.blocking_read_internal(address, buf, BusType::I2c, SendStop::Yes) } embedded_hal_1::i2c::Operation::Write(buf) => { self.blocking_write_internal(address, buf, BusType::I2c, SendStop::Yes) } } } } impl<'d, M: AsyncMode> embedded_hal_async::i2c::I2c for I3c<'d, M> where I3c<'d, M>: AsyncEngine, { async fn transaction( &mut self, address: u8, operations: &mut [embedded_hal_async::i2c::Operation<'_>], ) -> Result<(), Self::Error> { let Some((last, rest)) = operations.split_last_mut() else { return Ok(()); }; for op in rest { match op { embedded_hal_async::i2c::Operation::Read(buf) => { ::async_read_internal(self, address, buf, BusType::I2c, SendStop::No).await? } embedded_hal_async::i2c::Operation::Write(buf) => { ::async_write_internal(self, address, buf, BusType::I2c, SendStop::No).await? } } } match last { embedded_hal_async::i2c::Operation::Read(buf) => { ::async_read_internal(self, address, buf, BusType::I2c, SendStop::Yes).await } embedded_hal_async::i2c::Operation::Write(buf) => { ::async_write_internal(self, address, buf, BusType::I2c, SendStop::Yes).await } } } } impl<'d, M: Mode> embassy_embedded_hal::SetConfig for I3c<'d, M> { type Config = Config; type ConfigError = SetupError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_configuration(config) } } ================================================ FILE: embassy-mcxa/src/i3c/mod.rs ================================================ //! I3C Support use core::marker::PhantomData; use embassy_hal_internal::PeripheralType; use maitake_sync::WaitCell; use paste::paste; use crate::clocks::Gate; use crate::clocks::periph_helpers::I3cConfig; use crate::dma::{DmaChannel, DmaRequest}; use crate::gpio::{GpioPin, SealedPin}; use crate::{interrupt, pac}; pub mod controller; /// I3C interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let status = T::info().regs().mintmasked().read(); T::PERF_INT_INCR(); if status.nowmaster() || status.complete() || status.mctrldone() || status.slvstart() || status.errwarn() || status.rxpend() || status.txnotfull() { T::info().regs().mintclr().write(|w| { w.set_nowmaster(true); w.set_complete(true); w.set_mctrldone(true); w.set_slvstart(true); w.set_errwarn(true); w.set_rxpend(true); w.set_txnotfull(true); }); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } mod sealed { /// Seal a trait pub trait Sealed {} } trait SealedInstance: Gate { fn info() -> &'static Info; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); const TX_DMA_REQUEST: DmaRequest; const RX_DMA_REQUEST: DmaRequest; } /// I3C Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this I3C instance. type Interrupt: interrupt::typelevel::Interrupt; } struct Info { regs: pac::i3c::I3c, wait_cell: WaitCell, } unsafe impl Sync for Info {} impl Info { #[inline(always)] fn regs(&self) -> pac::i3c::I3c { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } macro_rules! impl_instance { ($($n:literal);*) => { $( paste! { impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], wait_cell: WaitCell::new(), }; &INFO } const TX_DMA_REQUEST: DmaRequest = DmaRequest::[]; const RX_DMA_REQUEST: DmaRequest = DmaRequest::[]; const PERF_INT_INCR: fn() = crate::perf_counters::[]; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0); #[cfg(feature = "mcxa5xx")] impl_instance!(1; 2; 3); /// SCL pin trait. pub trait SclPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// SDA pin trait. pub trait SdaPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// Driver mode. #[allow(private_bounds)] pub trait Mode: sealed::Sealed {} /// Async driver mode. #[allow(private_bounds)] pub trait AsyncMode: sealed::Sealed + Mode {} /// Blocking mode. pub struct Blocking; impl sealed::Sealed for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl sealed::Sealed for Async {} impl Mode for Async {} impl AsyncMode for Async {} /// DMA mode. pub struct Dma<'d> { tx_dma: DmaChannel<'d>, tx_request: DmaRequest, rx_dma: DmaChannel<'d>, rx_request: DmaRequest, } impl sealed::Sealed for Dma<'_> {} impl Mode for Dma<'_> {} impl AsyncMode for Dma<'_> {} macro_rules! impl_pin { ($pin:ident, $peri:ident, $fn:ident, $trait:ident) => { paste! { impl sealed::Sealed for crate::peripherals::$pin {} impl $trait for crate::peripherals::$pin { fn mux(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); self.set_function(crate::pac::port::vals::Mux::$fn); self.set_enable_input_buffer(true); } } } }; } #[cfg(feature = "mcxa2xx")] mod mcxa2xx_pins { use super::*; // impl_pin!(P0_2, I3C0, MUX10, PurPin); REVISIT: what is this for? impl_pin!(P0_17, I3C0, MUX10, SclPin); impl_pin!(P0_18, I3C0, MUX10, SdaPin); impl_pin!(P1_8, I3C0, MUX10, SdaPin); impl_pin!(P1_9, I3C0, MUX10, SclPin); // impl_pin!(P1_11, I3C0, MUX10, PurPin); REVISIT: what is this for? #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_30, I3C0, MUX10, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_31, I3C0, MUX10, SclPin); } #[cfg(feature = "mcxa5xx")] mod mcxa5xx_pins { use super::*; // impl_pin!(P0_2, I3C0, MUX10, PurPin); REVISIT: what is this for? impl_pin!(P0_16, I3C0, MUX10, SdaPin); impl_pin!(P0_17, I3C0, MUX10, SclPin); impl_pin!(P0_20, I3C0, MUX10, SdaPin); impl_pin!(P0_21, I3C0, MUX10, SclPin); // impl_pin!(P0_22, I3C0, MUX10, PurPin); REVISIT: what is this for? impl_pin!(P1_5, I3C1, MUX10, SdaPin); impl_pin!(P1_6, I3C1, MUX10, SdaPin); impl_pin!(P1_7, I3C1, MUX10, SdaPin); impl_pin!(P1_8, I3C1, MUX10, SdaPin); impl_pin!(P1_9, I3C1, MUX10, SclPin); impl_pin!(P1_14, I3C1, MUX10, SdaPin); // impl_pin!(P1_15, I3C1, MUX10, PurPin); REVISIT: what is this for? impl_pin!(P1_16, I3C1, MUX10, SdaPin); impl_pin!(P1_17, I3C1, MUX10, SclPin); impl_pin!(P1_18, I3C1, MUX10, SdaPin); impl_pin!(P1_19, I3C1, MUX10, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_30, I3C2, MUX10, SdaPin); #[cfg(feature = "sosc-as-gpio")] impl_pin!(P1_31, I3C2, MUX10, SclPin); // impl_pin!(P4_0, I3C2, MUX10, PurPin); REVISIT: what is this for? // impl_pin!(P4_1, I3C3, MUX10, PurPin); REVISIT: what is this for? // impl_pin!(P4_12, I3C2, MUX10, PurPin); REVISIT: what is this for? // impl_pin!(P4_13, I3C3, MUX10, PurPin); REVISIT: what is this for? impl_pin!(P4_2, I3C3, MUX10, SdaPin); impl_pin!(P4_3, I3C2, MUX10, SclPin); impl_pin!(P4_4, I3C2, MUX10, SdaPin); impl_pin!(P4_5, I3C3, MUX10, SclPin); impl_pin!(P4_6, I3C3, MUX10, SclPin); impl_pin!(P4_7, I3C3, MUX10, SdaPin); } ================================================ FILE: embassy-mcxa/src/inputmux.rs ================================================ //! Input Mux driver. use paste::paste; use crate::clocks::periph_helpers::NoConfig; use crate::clocks::{disable, enable, enable_and_reset}; use crate::pac; use crate::peripherals::INPUTMUX0; pub(crate) trait SealedValidInputMuxConfig { fn mux() {} } /// Marker trait for valid Input mux configurations. #[allow(private_bounds)] pub trait ValidInputMuxConfig: SealedValidInputMuxConfig {} pub(crate) fn init() { unsafe { // Enable the peripheral an deassert reset. let _ = enable_and_reset::(&NoConfig); // INPUTMUX only needs to have its clocks ungated when // accessing any of the memory mapped registers. Therefore, // it's safe to disable clocks here. disable::(); } } macro_rules! impl_input_mux { (ctimer, $value:literal, $pin:ident) => { impl_input_mux!(ctimer, 0, $value, $pin); impl_input_mux!(ctimer, 1, $value, $pin); impl_input_mux!(ctimer, 2, $value, $pin); impl_input_mux!(ctimer, 3, $value, $pin); impl_input_mux!(ctimer, 4, $value, $pin); }; (ctimer, $inst:literal, $value:literal, $pin:ident) => { paste! { impl_input_mux!([], [], $pin, [], 0, $value); impl_input_mux!([], [], $pin, [], 1, $value); impl_input_mux!([], [], $pin, [], 2, $value); impl_input_mux!([], [], $pin, [], 3, $value); } }; ($peri:ident, $ch:ident, $pin:ident, $reg:ident, $n:literal, $value:literal) => { paste! { impl SealedValidInputMuxConfig for ( crate::peripherals::$peri, crate::peripherals::$ch, crate::peripherals::$pin, ) { #[inline(always)] fn mux() { let _ = unsafe { enable::(&NoConfig) }; pac::INPUTMUX0.[<$reg>]($n).write(|w| w.set_inp([<$value>].into())); unsafe { disable::() }; } } impl ValidInputMuxConfig for ( crate::peripherals::$peri, crate::peripherals::$ch, crate::peripherals::$pin, ) { } } }; } #[cfg(feature = "swd-as-gpio")] impl_input_mux!(ctimer, 2, P0_1); #[cfg(feature = "jtag-extras-as-gpio")] impl_input_mux!(ctimer, 3, P0_6); impl_input_mux!(ctimer, 1, P0_20); impl_input_mux!(ctimer, 2, P0_21); impl_input_mux!(ctimer, 3, P0_22); impl_input_mux!(ctimer, 4, P0_23); impl_input_mux!(ctimer, 5, P1_0); impl_input_mux!(ctimer, 6, P1_1); impl_input_mux!(ctimer, 1, P1_2); impl_input_mux!(ctimer, 2, P1_3); impl_input_mux!(ctimer, 7, P1_6); impl_input_mux!(ctimer, 8, P1_7); impl_input_mux!(ctimer, 9, P1_8); impl_input_mux!(ctimer, 10, P1_9); impl_input_mux!(ctimer, 11, P1_14); impl_input_mux!(ctimer, 12, P1_15); #[cfg(feature = "sosc-as-gpio")] impl_input_mux!(ctimer, 17, P1_30); #[cfg(feature = "sosc-as-gpio")] impl_input_mux!(ctimer, 18, P1_31); impl_input_mux!(ctimer, 17, P2_0); impl_input_mux!(ctimer, 18, P2_1); impl_input_mux!(ctimer, 13, P2_2); impl_input_mux!(ctimer, 14, P2_3); impl_input_mux!(ctimer, 15, P2_4); impl_input_mux!(ctimer, 16, P2_5); impl_input_mux!(ctimer, 19, P2_6); impl_input_mux!(ctimer, 20, P2_7); impl_input_mux!(ctimer, 17, P3_0); impl_input_mux!(ctimer, 18, P3_1); impl_input_mux!(ctimer, 5, P3_8); impl_input_mux!(ctimer, 6, P3_9); impl_input_mux!(ctimer, 7, P3_14); impl_input_mux!(ctimer, 8, P3_15); impl_input_mux!(ctimer, 9, P3_16); impl_input_mux!(ctimer, 10, P3_17); impl_input_mux!(ctimer, 11, P3_22); #[cfg(feature = "mcxa2xx")] impl_input_mux!(ctimer, 14, P3_27); #[cfg(feature = "mcxa2xx")] impl_input_mux!(ctimer, 13, P3_28); #[cfg(all(feature = "dangerous-reset-as-gpio", feature = "mcxa2xx"))] impl_input_mux!(ctimer, 4, P3_29); impl_input_mux!(ctimer, 7, P4_6); impl_input_mux!(ctimer, 8, P4_7); ================================================ FILE: embassy-mcxa/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![doc = include_str!("../README.md")] // Clippy Exceptions // // Allow functions with too many args - we have a lot of HAL constructors like this for now #![allow(clippy::too_many_arguments)] /// Module for MCXA2xx-specific HAL drivers /// /// NOTE: *for now*, some items are here because we haven't validated them on the MCXA5xx yet. /// This note will be removed when the two reach parity. #[cfg(feature = "mcxa2xx")] #[path = "."] mod mcxa2xx_exclusive { pub mod flash; pub use crate::chips::mcxa2xx::{Peripherals, init, interrupt, peripherals}; } /// Module for MCXA5xx-specific HAL drivers #[cfg(feature = "mcxa5xx")] #[path = "."] mod mcxa5xx_exclusive { pub use crate::chips::mcxa5xx::{Peripherals, init, interrupt, peripherals}; } /// Module for HAL drivers supported by all chips #[path = "."] mod all_chips { pub mod adc; pub mod cdog; pub mod clkout; pub mod clocks; pub mod config; pub mod crc; pub mod ctimer; pub mod dma; #[cfg(feature = "executor-platform")] pub mod executor; pub mod gpio; pub mod i2c; pub mod i3c; pub mod inputmux; pub mod lpuart; pub mod ostimer; pub mod perf_counters; pub mod reset_reason; pub mod rtc; pub mod spi; pub mod trng; pub mod wwdt; } #[allow(unused_imports)] pub use all_chips::*; #[cfg(feature = "mcxa2xx")] pub use mcxa2xx_exclusive::*; #[cfg(feature = "mcxa5xx")] pub use mcxa5xx_exclusive::*; pub(crate) mod chips; // Re-export interrupt traits and types // Re-export Peri and PeripheralType to allow applications to express Peri types and requirements. pub use embassy_hal_internal::{Peri, PeripheralType}; #[cfg(feature = "unstable-pac")] pub use nxp_pac as pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use nxp_pac as pac; const HALS_SELECTED: usize = const { cfg!(feature = "mcxa2xx") as usize + cfg!(feature = "mcxa5xx") as usize }; /// Ensure exactly one chip feature is set. #[doc(hidden)] pub const _SINGLE_HAL_CHECK: bool = const { assert!(HALS_SELECTED == 1, "Select exactly one chip feature!"); HALS_SELECTED == 1 }; /// Macro to bind interrupts to handlers, similar to embassy-imxrt. /// /// Example: /// - Bind OS_EVENT to the OSTIMER time-driver handler /// bind_interrupts!(struct Irqs { OS_EVENT => crate::ostimer::time_driver::OsEventHandler; }); #[macro_export] macro_rules! bind_interrupts { ($(#[$attr:meta])* $vis:vis struct $name:ident { $( $(#[cfg($cond_irq:meta)])? $irq:ident => $( $(#[cfg($cond_handler:meta)])? $handler:ty ),*; )* }) => { #[derive(Copy, Clone)] $(#[$attr])* $vis struct $name; $( #[allow(non_snake_case)] #[unsafe(no_mangle)] $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { unsafe { $( $(#[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)* } } ================================================ FILE: embassy-mcxa/src/lpuart/bbq.rs ================================================ //! Buffered Lpuart driver powered by `bbqueue` #![deny(clippy::undocumented_unsafe_blocks)] use core::marker::PhantomData; use core::ptr::NonNull; use core::sync::atomic::{AtomicU8, AtomicU32, Ordering, fence}; use bbqueue::BBQueue; use bbqueue::prod_cons::stream::{StreamGrantR, StreamGrantW}; use bbqueue::traits::coordination::cas::AtomicCoord; use bbqueue::traits::notifier::maitake::MaiNotSpsc; use bbqueue::traits::storage::Storage; use embassy_hal_internal::Peri; use grounded::uninit::GroundedCell; use maitake_sync::WaitCell; use nxp_pac::lpuart::vals::Tc; use paste::paste; use super::{DataBits, IdleConfig, Info, MsbFirst, Parity, RxPin, StopBits, TxPin, TxPins}; use crate::clocks::periph_helpers::{Div4, LpuartClockSel}; use crate::clocks::{PoweredClock, WakeGuard}; use crate::dma::{DMA_MAX_TRANSFER_SIZE, DmaChannel, DmaRequest, InvalidParameters, TransferOptions}; use crate::gpio::{AnyPin, HasGpioInstance, PeriGpioExt}; use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; use crate::lpuart::{Instance, RxPins}; /// Error Type #[derive(Debug, PartialEq)] #[non_exhaustive] pub enum BbqError { /// Errors from LPUart setup Basic(super::Error), /// Could not initialize a new instance as the current instance is already in use Busy, /// Attempted to create an Rx half with Tx parts, or a Tx half with Rx parts WrongParts, /// Requested an [`RxMode::MaxFrame`] too large for the provided buffer MaxFrameTooLarge, } impl core::fmt::Display for BbqError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { ::fmt(self, f) } } impl core::error::Error for BbqError {} /// RX Reception mode #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] pub enum BbqRxMode { /// Default mode, attempts to utilize the ring buffer as maximally as possible. /// /// In this mode, the interrupt will use whatever space is available, up to 1/4 /// the total ring buffer size, or the max DMA transfer size, whichever is smaller. /// however this may mean that if we are at the "end" of the ring buffer, /// some transfers may be smaller, meaning we need to "reload" the interrupt /// more often. /// /// At slower UART rates (like 115_200), this is probably acceptable, as we have /// roughly 347us to service the "end of transfer" interrupt and reload the next /// DMA transfer. However at higher speeds (like 4_000_000), this time shrinks to /// 10us, meaning that critical sections (including defmt logging) may cause us to /// lose data. /// /// If you know your maximum frame/burst size, you can instead use [`RxMode::MaxFrame`], /// which will never allow "short" grants, with the trade off that we may reduce the /// total usable capacity temporarily if we need to wrap around the ring buffer early. #[default] Efficiency, /// Max Frame mode, ensures that dma transfers always have exactly `size` bytes available /// /// In this mode, we will always make DMA transfers of the given size. This is intended for /// cases where we are receving bursts of data <= `size`, ideally with a short gap between /// bursts. This means that we will receive an IDLE interrupt, and switch over receiving grants /// in the quiet period, avoiding potentially latency-sensitive DMA transfer updates while /// data is still being transferred. This is especially useful at higher baudrates. /// /// The tradeoff here is that we can temporarily "waste" up to `(size - 1)` bytes if we /// are forced to wrap-around the ring buffer early. For example if there is only 1023 bytes /// in the ring buffer before it wraps around, and `size = 1024`, we will be forced to wrap /// around the ring early, skipping that capacity. In some cases, where the required 1024 /// bytes are not available at the beginning of the ring buffer either, we will not begin /// a transfer at all, potentially losing data if capacity is not freed up before the next /// transfer starts (each time the ring buffer is drained, we will automatically re-start /// receiving if enough capacity is made available). /// /// `size` must be <= (capacity / 4). MaxFrame { size: usize }, } /// Lpuart config #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub struct BbqConfig { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: LpuartClockSel, /// Clock divisor pub div: Div4, /// Baud rate in bits per second pub baudrate_bps: u32, /// Parity configuration pub parity_mode: Option, /// Number of data bits pub data_bits_count: DataBits, /// MSB First or LSB First configuration pub msb_first: MsbFirst, /// Number of stop bits pub stop_bits_count: StopBits, /// RX IDLE configuration pub rx_idle_config: IdleConfig, } impl Default for BbqConfig { fn default() -> Self { Self { baudrate_bps: 115_200u32, parity_mode: None, data_bits_count: DataBits::DATA8, msb_first: MsbFirst::LSB_FIRST, stop_bits_count: StopBits::ONE, rx_idle_config: IdleConfig::IDLE_1, power: PoweredClock::AlwaysEnabled, source: LpuartClockSel::FroLfDiv, div: Div4::no_div(), } } } impl From for super::Config { fn from(value: BbqConfig) -> Self { let mut cfg = super::Config::default(); let BbqConfig { power, source, div, baudrate_bps, parity_mode, data_bits_count, msb_first, stop_bits_count, rx_idle_config, } = value; // User selectable cfg.power = power; cfg.source = source; cfg.div = div; cfg.baudrate_bps = baudrate_bps; cfg.parity_mode = parity_mode; cfg.data_bits_count = data_bits_count; cfg.msb_first = msb_first; cfg.stop_bits_count = stop_bits_count; cfg.rx_idle_config = rx_idle_config; // Manually set cfg.tx_fifo_watermark = 0; cfg.rx_fifo_watermark = 0; cfg.swap_txd_rxd = false; cfg } } /// A `bbqueue` powered buffered Lpuart pub struct LpuartBbq { // TODO: Don't just make these pub, we don't *really* handle dropping/recreation // of separate parts at the moment. /// The TX half of the LPUART tx: LpuartBbqTx, /// The RX half of the LPUART rx: LpuartBbqRx, } #[derive(Copy, Clone)] struct BbqVtable { lpuart_init: fn(bool, bool, bool, bool, super::Config) -> Result, super::Error>, int_pend: fn(), int_unpend: fn(), int_disable: fn(), dma_rx_cb: fn(), int_enable: unsafe fn(), } impl BbqVtable { fn for_lpuart() -> Self { Self { int_pend: T::Interrupt::pend, int_unpend: T::Interrupt::unpend, int_disable: T::Interrupt::disable, int_enable: T::Interrupt::enable, dma_rx_cb: T::dma_rx_complete_cb, lpuart_init: super::Lpuart::<'static, super::Blocking>::init::, } } } #[derive(PartialEq, Copy, Clone)] enum WhichHalf { Rx, Tx, } pub struct BbqHalfParts { // resources buffer: &'static mut [u8], dma_ch: DmaChannel<'static>, pin: Peri<'static, AnyPin>, // type erasure which: WhichHalf, dma_req: u8, mux: crate::pac::port::vals::Mux, info: &'static Info, state: &'static BbqState, vtable: BbqVtable, } pub struct BbqParts { // resources tx_buffer: &'static mut [u8], tx_dma_ch: DmaChannel<'static>, tx_pin: Peri<'static, AnyPin>, rx_buffer: &'static mut [u8], rx_dma_ch: DmaChannel<'static>, rx_pin: Peri<'static, AnyPin>, // type erasure tx_dma_req: u8, tx_mux: crate::pac::port::vals::Mux, rx_dma_req: u8, rx_mux: crate::pac::port::vals::Mux, info: &'static Info, state: &'static BbqState, vtable: BbqVtable, } impl BbqParts { pub fn new, Rx: RxPin>( _inner: Peri<'static, T>, _irq: impl Binding> + 'static, tx_pin: Peri<'static, Tx>, tx_buffer: &'static mut [u8], tx_dma_ch: impl Into>, rx_pin: Peri<'static, Rx>, rx_buffer: &'static mut [u8], rx_dma_ch: impl Into>, ) -> Result { Ok(Self { tx_buffer, tx_dma_ch: tx_dma_ch.into(), tx_pin: tx_pin.into(), rx_buffer, rx_dma_ch: rx_dma_ch.into(), rx_pin: rx_pin.into(), tx_dma_req: T::TX_DMA_REQUEST.number(), tx_mux: Tx::MUX, rx_dma_req: T::RX_DMA_REQUEST.number(), rx_mux: Rx::MUX, info: T::info(), state: T::bbq_state(), vtable: BbqVtable::for_lpuart::(), }) } /// Access a borrow of the contained RX pin pub fn rx_pin(&mut self) -> Peri<'_, AnyPin> { self.rx_pin.reborrow() } /// Access a borrow of the contained TX pin pub fn tx_pin(&mut self) -> Peri<'_, AnyPin> { self.tx_pin.reborrow() } /// Access a borrow of both the RX and TX pin (in that order) pub fn pins(&mut self) -> (Peri<'_, AnyPin>, Peri<'_, AnyPin>) { let Self { tx_pin, rx_pin, .. } = self; (rx_pin.reborrow(), tx_pin.reborrow()) } } impl BbqHalfParts { pub fn pin(&mut self) -> Peri<'_, AnyPin> { self.pin.reborrow() } pub fn new_tx_half>( _inner: Peri<'static, T>, _irq: impl Binding> + 'static, tx_pin: Peri<'static, P>, buffer: &'static mut [u8], dma_ch: impl Into>, ) -> Self { Self { buffer, dma_ch: dma_ch.into(), pin: tx_pin.into(), mux: P::MUX, info: T::info(), state: T::bbq_state(), dma_req: T::TX_DMA_REQUEST.number(), vtable: BbqVtable::for_lpuart::(), which: WhichHalf::Tx, } } pub fn new_rx_half>( _inner: Peri<'static, T>, _irq: impl Binding> + 'static, tx_pin: Peri<'static, P>, buffer: &'static mut [u8], dma_ch: impl Into>, ) -> Self { Self { buffer, dma_ch: dma_ch.into(), pin: tx_pin.into(), mux: P::MUX, info: T::info(), state: T::bbq_state(), dma_req: T::RX_DMA_REQUEST.number(), vtable: BbqVtable::for_lpuart::(), which: WhichHalf::Rx, } } /// Setup Rx half while binding GPIO to the gpio pin. /// This allows later use of async functions on the pin. pub fn new_rx_half_async + HasGpioInstance>( _inner: Peri<'static, T>, irq: impl Binding> + Binding<::Interrupt, crate::gpio::InterruptHandler> + 'static, tx_pin: Peri<'static, P>, buffer: &'static mut [u8], dma_ch: impl Into>, ) -> Self { Self { buffer, dma_ch: dma_ch.into(), pin: tx_pin.degrade_async(irq), mux: P::MUX, info: T::info(), state: T::bbq_state(), dma_req: T::RX_DMA_REQUEST.number(), vtable: BbqVtable::for_lpuart::(), which: WhichHalf::Rx, } } } impl LpuartBbq { /// Create a new LpuartBbq with both transmit and receive halves pub fn new(parts: BbqParts, config: BbqConfig, mode: BbqRxMode) -> Result { // Get state for this instance, and try to move from the "uninit" to "initing" state parts.state.uninit_to_initing()?; // Set as TX/RX pin mode any_as_tx(&parts.tx_pin, parts.tx_mux); any_as_rx(&parts.rx_pin, parts.rx_mux); // Configure UART peripheral // TODO make this a specific Bbq mode instead of using blocking // TODO support CTS/RTS pins? let _wg = (parts.vtable.lpuart_init)(true, true, false, false, config.into()).map_err(BbqError::Basic)?; // Setup the TX state // // SAFETY: We have ensured we are in the INITING state, and interrupts are not yet active. unsafe { LpuartBbqTx::initialize_tx_state(parts.state, parts.tx_dma_ch, parts.tx_buffer, parts.tx_dma_req); } // Setup the RX state let len = parts.rx_buffer.len(); // SAFETY: We have ensured we are in the INITING state, and the interrupt is not yet active. unsafe { LpuartBbqRx::initialize_rx_state( parts.state, parts.info, parts.rx_dma_ch, parts.vtable.dma_rx_cb, parts.rx_buffer, parts.rx_dma_req, ); } // Update our state to "initialized", and that we have the TXDMA + RXDMA channels present // Okay to just store: we have exclusive access let max_size = (len / 4).min(DMA_MAX_TRANSFER_SIZE); let rx_mode_bits = match mode { BbqRxMode::Efficiency => (max_size as u32) << 16, BbqRxMode::MaxFrame { size } => { if size > max_size { return Err(BbqError::MaxFrameTooLarge); } let size = (size as u32) << 16; size | STATE_RXDMA_MODE_MAXFRAME } }; let new_state = STATE_INITED | STATE_TXDMA_PRESENT | STATE_RXDMA_PRESENT | rx_mode_bits; parts.state.state.store(new_state, Ordering::Release); // SAFETY: We have ensured that our ISR is present via the IRQ token, and we have // initialized the shared state machine sufficiently that it can execute correctly // when triggered. unsafe { // Clear any stale interrupt flags (parts.vtable.int_unpend)(); // Enable the LPUART interrupt (parts.vtable.int_enable)(); // Immediately pend the interrupt, this will "load" the DMA transfer as the // ISR will notice that there is no active grant. This means that we start // receiving immediately without additional user interaction. (parts.vtable.int_pend)(); } Ok(Self { tx: LpuartBbqTx { state: parts.state, info: parts.info, vtable: parts.vtable, mux: parts.tx_mux, _tx_pins: TxPins { tx_pin: parts.tx_pin, cts_pin: None, }, _wg: _wg.clone(), }, rx: LpuartBbqRx { state: parts.state, info: parts.info, vtable: parts.vtable, mux: parts.rx_mux, _rx_pins: RxPins { rx_pin: parts.rx_pin, rts_pin: None, }, _wg, }, }) } /// Write some data to the buffer. See [`LpuartBbqTx::write`] for more information pub fn write(&mut self, buf: &[u8]) -> impl Future> { self.tx.write(buf) } /// Read some data from the buffer. See [`LpuartBbqRx::read`] for more information pub fn read(&mut self, buf: &mut [u8]) -> impl Future> { self.rx.read(buf) } /// Wait for all bytes in the outgoing buffer to be flushed asynchronously. /// /// See [`LpuartBbqTx::flush`] for more information pub fn flush(&mut self) -> impl Future { self.tx.flush() } /// Busy wait until all transmitting has completed /// /// See [`LpuartBbqTx::blocking_flush`] for more information pub fn blocking_flush(&mut self) { self.tx.blocking_flush(); } /// Borrow split parts. pub fn split_ref(&mut self) -> (&mut LpuartBbqRx, &mut LpuartBbqTx) { let Self { tx, rx } = self; (rx, tx) } /// Teardown the LpuartBbq, retrieving the original parts pub fn teardown(self) -> BbqParts { let Self { tx, rx } = self; let tx_parts = tx.teardown(); let rx_parts = rx.teardown(); BbqParts { tx_buffer: tx_parts.buffer, tx_dma_ch: tx_parts.dma_ch, tx_pin: tx_parts.pin, rx_buffer: rx_parts.buffer, rx_dma_ch: rx_parts.dma_ch, rx_pin: rx_parts.pin, tx_dma_req: tx_parts.dma_req, tx_mux: tx_parts.mux, rx_dma_req: rx_parts.dma_req, rx_mux: rx_parts.mux, info: tx_parts.info, state: tx_parts.state, vtable: tx_parts.vtable, } } } /// A `bbqueue` powered Lpuart TX Half pub struct LpuartBbqTx { state: &'static BbqState, info: &'static Info, vtable: BbqVtable, mux: crate::pac::port::vals::Mux, _tx_pins: TxPins<'static>, _wg: Option, } impl LpuartBbqTx { /// ## SAFETY /// /// This function must only be called in the "INITING" state, and BEFORE /// enabling interrupts, meaning we have exclusive access to the TX components /// of the given BbqState. unsafe fn initialize_tx_state( state: &'static BbqState, dma: DmaChannel<'static>, tx_buffer: &'static mut [u8], request_num: u8, ) { // Enable the DMA interrupt to handle "transfer complete" interrupts dma.enable_interrupt(); // Setup the TX bbqueue instance, store the DMA channel and bbqueue in the // BbqState storage location. // // TODO: We could probably be more clever and setup the DMA transfer request // number ONCE in init, then just do a minimal-reload. This would allow us to // avoid storing the txdma_num, and save some effort in the ISR. let cont = Container::from(tx_buffer); // SAFETY: We have exclusive access to the shared TX components, and the interrupt // is not yet enabled. We move ownership of these resources to the shared area. unsafe { state.tx_queue.get().write(BBQueue::new_with_storage(cont)); state.txdma.get().write(dma); state.txdma_num.store(request_num, Ordering::Release); } } /// Create a new LpuartBbq with only the transmit half /// /// NOTE: Dropping the `LpuartBbqTx` will *permanently* leak the TX buffer, DMA channel, and tx pin. /// Call [LpuartBbqTx::teardown] to reclaim these resources. pub fn new(parts: BbqHalfParts, config: BbqConfig) -> Result { // Are these the right parts? if parts.which != WhichHalf::Tx { return Err(BbqError::WrongParts); } // Get state for this instance, and try to move from the "uninit" to "initing" state parts.state.uninit_to_initing()?; // Set as TX pin mode any_as_tx(&parts.pin, parts.mux); // Configure UART peripheral // TODO make this a specific Bbq mode instead of using blocking // TODO support CTS pin? let _wg = (parts.vtable.lpuart_init)(true, false, false, false, config.into()).map_err(BbqError::Basic)?; // Setup the TX Half state // // SAFETY: We have ensured we are in the INITING state, and the interrupt is not yet active. unsafe { Self::initialize_tx_state(parts.state, parts.dma_ch, parts.buffer, parts.dma_req); } // Update our state to "initialized", and that we have the TXDMA channel present // Okay to just store: we have exclusive access let new_state = STATE_INITED | STATE_TXDMA_PRESENT; parts.state.state.store(new_state, Ordering::Release); // SAFETY: We have properly initialized the shared storage, and ensured that // our ISR is installed with the Irq token. unsafe { // Clear any stale interrupt flags (parts.vtable.int_unpend)(); // Enable the LPUART interrupt (parts.vtable.int_enable)(); // NOTE: Unlike RX, we don't begin transmitting immediately, we move // from Idle -> Transmitting the first time the user calls write. } Ok(Self { state: parts.state, info: parts.info, vtable: parts.vtable, _tx_pins: TxPins { tx_pin: parts.pin, cts_pin: None, }, _wg, mux: parts.mux, }) } /// Write some data to the outgoing transmit buffer /// /// This method waits until some data is able to be written to the internal buffer, /// and returns the number of bytes from `buf` consumed. /// /// This does NOT guarantee all bytes of `buf` have been buffered, only the amount returned. /// /// This does NOT guarantee the bytes have been written to the wire. See [`Self::flush()`]. pub async fn write(&mut self, buf: &[u8]) -> Result { // TODO: we could have a version of this that gives the user the grant directly // to reduce the effort of copying. // SAFETY: The existence of a LpuartBbqTx ensures that the `tx_queue` has been // initialized. The tx_queue is safe to access in a shared manner after initialization. let tx_queue = unsafe { &*self.state.tx_queue.get() }; let prod = tx_queue.stream_producer(); let mut wgr = prod.wait_grant_max_remaining(buf.len()).await; let to_copy = buf.len().min(wgr.len()); wgr[..to_copy].copy_from_slice(&buf[..to_copy]); wgr.commit(to_copy); (self.vtable.int_pend)(); Ok(to_copy) } /// Wait for all bytes in the outgoing buffer to be flushed asynchronously. /// /// When this method completes, the outgoing buffer is empty. pub async fn flush(&mut self) { // Discard the result on wait_for as we never close the waiter. let _ = self .state .tx_flushed .wait_for(|| { // We are idle when there is no TXGR active (self.state.state.load(Ordering::Acquire) & STATE_TXGR_ACTIVE) == 0 }) .await; } /// Busy wait until all transmitting has completed /// /// When this method completes, the outgoing buffer is empty. pub fn blocking_flush(&mut self) { while (self.state.state.load(Ordering::Acquire) & STATE_TXGR_ACTIVE) != 0 {} } /// Teardown the Tx handle, reclaiming the parts. pub fn teardown(self) -> BbqHalfParts { // First, disable relevant interrupts let state = critical_section::with(|_cs| { self.info.regs.ctrl().modify(|w| w.set_tcie(false)); // Clear the TXDMA present bit to prevent the ISR from touching anything. // Relaxed is okay here because CS::with has Acq/Rel semantics on entry and exit self.state.state.fetch_and(!STATE_TXDMA_PRESENT, Ordering::Relaxed) }); // If there is an active grant, the TX DMA may be active. Stop it and release the grant if (state & STATE_TXGR_ACTIVE) != 0 { // SAFETY: We have unset TXDMA_PRESENT and disabled TCIE: we now have exclusive // access to the shared tx resources. unsafe { // Take DMA channel by mut ref let txdma = &mut *self.state.txdma.get(); // Stop the DMA self.info.regs().baud().modify(|w| w.set_tdmae(false)); txdma.disable_request(); txdma.clear_done(); fence(Ordering::Acquire); // Then take the grant by ownership, and drop it, which releases the grant _ = self.state.txgr.get().read(); } self.state.state.fetch_and(!STATE_TXGR_ACTIVE, Ordering::AcqRel); } // Get a reference to the tx_queue to retrieve the Container // // SAFETY: We have unset TXDMA_PRESENT and disabled TCIE: we now have exclusive // access to the shared tx resources. let (ptr, len) = unsafe { let tx_queue = &*self.state.tx_queue.get(); tx_queue.storage().ptr_len() }; // Now, drop the queue in place. This is sound because as the LpuartBbqTx, we have exclusive // access to the "producer" half, and by disabling the interrupt and notching out the state // bits, we know the ISR will no longer touch the consumer part. // // Also, take the DmaChannel by ownership this time. // // SAFETY: We have unset TXDMA_PRESENT and disabled TCIE: we now have exclusive // access to the shared tx resources. let tx_dma = unsafe { core::ptr::drop_in_place(self.state.tx_queue.get()); // Defensive coding: purge the tx_queue just in case. This doesn't zero the // whole buffer, only the tracking pointers. core::ptr::write_bytes(self.state.tx_queue.get(), 0, 1); let mut dma = self.state.txdma.get().read(); dma.clear_callback(); dma }; // Re-magic the mut slice from the storage we have now reclaimed by dropping the // tx_queue. // // SAFETY: We have unset TXDMA_PRESENT and disabled TCIE: we now have exclusive // access to the shared tx resources. let tx_buffer = unsafe { core::slice::from_raw_parts_mut(ptr.as_ptr(), len) }; // Now, if this was the last part of the lpuart, we are responsible for peripheral // cleanup. if (state & !(STATE_TXGR_ACTIVE | STATE_TXDMA_PRESENT)) == STATE_INITED { (self.vtable.int_disable)(); super::disable_peripheral(self.info); self.state.state.store(STATE_UNINIT, Ordering::Relaxed); } let (tx_pin, _) = self._tx_pins.take(); BbqHalfParts { buffer: tx_buffer, dma_ch: tx_dma, pin: tx_pin, dma_req: self.state.txdma_num.load(Ordering::Relaxed), mux: self.mux, info: self.info, state: self.state, vtable: self.vtable, which: WhichHalf::Tx, } } } use embedded_io_async::ErrorType; impl embedded_io_async::Error for BbqError { fn kind(&self) -> embedded_io::ErrorKind { match self { BbqError::Basic(error) => error.kind(), BbqError::Busy => embedded_io::ErrorKind::Other, BbqError::WrongParts => embedded_io::ErrorKind::Other, BbqError::MaxFrameTooLarge => embedded_io::ErrorKind::OutOfMemory, } } } impl ErrorType for LpuartBbqTx { type Error = BbqError; } impl embedded_io_async::Write for LpuartBbqTx { fn write(&mut self, buf: &[u8]) -> impl Future> { self.write(buf) } async fn flush(&mut self) -> Result<(), Self::Error> { self.flush().await; Ok(()) } } pub struct LpuartBbqRx { state: &'static BbqState, info: &'static Info, vtable: BbqVtable, mux: crate::pac::port::vals::Mux, _rx_pins: RxPins<'static>, _wg: Option, } impl LpuartBbqRx { /// ## SAFETY /// /// This function must only be called in the "INITING" state, and BEFORE /// enabling interrupts, meaning we have exclusive access to the RX components /// of the given BbqState. unsafe fn initialize_rx_state( state: &'static BbqState, _info: &'static Info, mut dma: DmaChannel<'static>, rx_callback: fn(), rx_buffer: &'static mut [u8], request_num: u8, ) { // Set the callback to our completion handler, so our LPUART interrupt gets called to // complete the transfer and reload // // TODO: Right now we only do this on RX, we might want to also handle this on TX as well // so we have more time to reload, but for now we'll naturally get the "transfer complete" // interrupt when the TX fifo empties, and we are less latency sensitive on TX than RX. // // SAFETY: We have exclusive ownership of the DmaChannel, and are able to overwrite the // existing callback, if any. unsafe { dma.set_callback(rx_callback); } // Enable the DMA interrupt to handle "transfer complete" interrupts dma.enable_interrupt(); // Setup the RX bbqueue instance, store the DMA channel and bbqueue in the // BbqState storage location. // // TODO: We could probably be more clever and setup the DMA transfer request // number ONCE in init, then just do a minimal-reload. This would allow us to // avoid storing the rxdma_num, and save some effort in the ISR. let cont = Container::from(rx_buffer); // SAFETY: We have exclusive access to the shared RX components, and the interrupt // is not yet enabled. We move ownership of these resources to the shared area. unsafe { state.rx_queue.get().write(BBQueue::new_with_storage(cont)); state.rxdma.get().write(dma); state.rxdma_num.store(request_num, Ordering::Release); } // TODO: Do we actually want these interrupts enabled? We probably do, so we can // clear the errors, but I'm not sure if any of these actually stall the receive. // // That being said, I've observed the RX line being floating (e.g. if the sender // is in reset or disconnected) causing ~infinite "framing errors", which causes // an interrupt storm since we don't *disable* the interrupt. We probably need to // think about how/if we handle these kinds of errors. // // info.regs().ctrl().modify(|w| { // // overrun // w.set_orie(true); // // noise // w.set_neie(true); // // framing // w.set_feie(true); // }); } /// Create a new LpuartBbq with only the receive half /// /// NOTE: Dropping the `LpuartBbqRx` will *permanently* leak the TX buffer, DMA channel, and tx pin. /// Call [LpuartBbqTx::teardown] to reclaim these resources. pub fn new(parts: BbqHalfParts, config: BbqConfig, mode: BbqRxMode) -> Result { // Are these the right parts? if parts.which != WhichHalf::Rx { return Err(BbqError::WrongParts); } // Get state for this instance, and try to move from the "uninit" to "initing" state parts.state.uninit_to_initing()?; // Set RX pin mode any_as_rx(&parts.pin, parts.mux); // Configure UART peripheral // TODO make this a specific Bbq mode instead of using blocking // TODO support RTS pin? let _wg = (parts.vtable.lpuart_init)(false, true, false, false, config.into()).map_err(BbqError::Basic)?; // Setup the RX half state let len = parts.buffer.len(); // SAFETY: We have ensured that we are in the INITING state, and the interrupt is not yet active. unsafe { Self::initialize_rx_state( parts.state, parts.info, parts.dma_ch, parts.vtable.dma_rx_cb, parts.buffer, parts.dma_req, ); } // Update our state to "initialized", and that we have the RXDMA channel present // Okay to just store: we have exclusive access let max_size = (len / 4).min(DMA_MAX_TRANSFER_SIZE); let rx_mode_bits = match mode { BbqRxMode::Efficiency => (max_size as u32) << 16, BbqRxMode::MaxFrame { size } => { if size > max_size { return Err(BbqError::MaxFrameTooLarge); } let size = (size as u32) << 16; size | STATE_RXDMA_MODE_MAXFRAME } }; let new_state = STATE_INITED | STATE_RXDMA_PRESENT | rx_mode_bits; parts.state.state.store(new_state, Ordering::Release); // SAFETY: We have ensured that our ISR is present via the IRQ token, and we have // initialized the shared state machine sufficiently that it can execute correctly // when triggered. unsafe { // Clear any stale interrupt flags (parts.vtable.int_unpend)(); // Enable the LPUART interrupt (parts.vtable.int_enable)(); // Immediately pend the interrupt, this will "load" the DMA transfer as the // ISR will notice that there is no active grant. This means that we start // receiving immediately without additional user interaction. (parts.vtable.int_pend)(); } Ok(Self { state: parts.state, info: parts.info, vtable: parts.vtable, mux: parts.mux, _rx_pins: RxPins { rx_pin: parts.pin, rts_pin: None, }, _wg, }) } /// Read some data from the incoming receive buffer /// /// This method waits until some data is able to be read from the internal buffer, /// and returns the number of bytes from `buf` written. /// /// This does NOT guarantee all bytes of `buf` have been written, only the amount returned. /// /// When receiving, this method must be called somewhat regularly to ensure that the incoming /// buffer does not become over full. /// /// In this case, data will be discarded until this read method is called and capacity is made /// available. pub async fn read(&mut self, buf: &mut [u8]) -> Result { // TODO: we could have a version of this that gives the user the grant directly // to reduce the effort of copying. // SAFETY: The existence of a LpuartBbqRx ensures that the `rx_queue` has been // initialized. The rx_queue is safe to access in a shared manner after initialization. let queue = unsafe { &*self.state.rx_queue.get() }; let cons = queue.stream_consumer(); let rgr = cons.wait_read().await; let to_copy = buf.len().min(rgr.len()); buf[..to_copy].copy_from_slice(&rgr[..to_copy]); rgr.release(to_copy); // If NO rx_dma is active, that means we stalled, so pend the interrupt to // restart it now that we've freed space. if (self.state.state.load(Ordering::Acquire) & STATE_RXGR_ACTIVE) == 0 { (self.vtable.int_pend)(); } Ok(to_copy) } /// Teardown the Rx handle, reclaiming the DMA channel, receive buffer, and Rx pin. pub fn teardown(self) -> BbqHalfParts { // First, mark the RXDMA as not present to halt the ISR from processing the state // machine let rx_state_bits = STATE_RXDMA_PRESENT | STATE_RXGR_ACTIVE | STATE_RXDMA_COMPLETE | STATE_RXDMA_MODE_MAXFRAME | STATE_RXGR_LEN_MASK; let state = self.state.state.fetch_and(!rx_state_bits, Ordering::AcqRel); // Then, disable receive-relevant interrupts critical_section::with(|_cs| { self.info.regs.ctrl().modify(|w| { w.set_ilie(false); w.set_neie(false); w.set_feie(false); w.set_orie(false); }); }); // If there is an active grant, the RX DMA may be active. Stop it and release the grant if (state & STATE_RXGR_ACTIVE) != 0 { // SAFETY: We have unset RXDMA_PRESENT and disabled all RX interrupts: we now have exclusive // access to the shared rx resources. unsafe { // Take DMA channel by mut ref let rxdma = &mut *self.state.rxdma.get(); // Stop the DMA self.info.regs().baud().modify(|w| w.set_rdmae(false)); rxdma.disable_request(); rxdma.clear_done(); fence(Ordering::Acquire); // Then take the grant by ownership, and drop it, which releases the grant _ = self.state.rxgr.get().read(); } } // Get a reference to the rx_queue to retrieve the Container // // SAFETY: We have unset RXDMA_PRESENT and disabled all RX interrupts: we now have exclusive // access to the shared rx resources. let (ptr, len) = unsafe { let rx_queue = &*self.state.rx_queue.get(); rx_queue.storage().ptr_len() }; // Now, drop the queue in place. This is sound because as the LpuartBbqRx, we have exclusive // access to the "consumer" half, and by disabling the interrupt and notching out the state // bits, we know the ISR will no longer touch the producer part. // // Also, take the DmaChannel by ownership this time. // // SAFETY: We have unset RXDMA_PRESENT and disabled all RX interrupts: we now have exclusive // access to the shared rx resources. let rx_dma = unsafe { core::ptr::drop_in_place(self.state.rx_queue.get()); // Defensive coding: purge the rx_queue just in case. This doesn't zero the // whole buffer, only the tracking pointers. core::ptr::write_bytes(self.state.rx_queue.get(), 0, 1); let mut dma = self.state.rxdma.get().read(); dma.clear_callback(); dma }; // Re-magic the mut slice from the storage we have now reclaimed by dropping the // rx_queue. // // SAFETY: We have unset RXDMA_PRESENT and disabled all RX interrupts: we now have exclusive // access to the shared rx resources. let rx_buffer = unsafe { core::slice::from_raw_parts_mut(ptr.as_ptr(), len) }; // Now, if this was the last part of the lpuart, we are responsible for peripheral // cleanup. if (state & !rx_state_bits) == STATE_INITED { super::disable_peripheral(self.info); self.state.state.store(STATE_UNINIT, Ordering::Relaxed); } let (rx_pin, _) = self._rx_pins.take(); BbqHalfParts { buffer: rx_buffer, dma_ch: rx_dma, pin: rx_pin, dma_req: self.state.rxdma_num.load(Ordering::Relaxed), mux: self.mux, info: self.info, state: self.state, vtable: self.vtable, which: WhichHalf::Rx, } } } impl embedded_io_async::ErrorType for LpuartBbqRx { type Error = BbqError; } impl embedded_io_async::Read for LpuartBbqRx { fn read(&mut self, buf: &mut [u8]) -> impl Future> { self.read(buf) } } // A wrapper type representing a `&'static mut [u8]` buffer struct Container { ptr: NonNull, len: usize, } impl Storage for Container { /// SAFETY: The length and ptr destination of the Container are never changed. unsafe fn ptr_len(&self) -> (NonNull, usize) { (self.ptr, self.len) } } impl From<&'static mut [u8]> for Container { fn from(value: &'static mut [u8]) -> Self { Self { len: value.len(), // SAFETY: The input slice is guaranteed to contain a non-null value ptr: unsafe { NonNull::new_unchecked(value.as_mut_ptr()) }, } } } /// interrupt handler. pub struct BbqInterruptHandler { _phantom: PhantomData, } const STATE_UNINIT: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0000; const STATE_INITING: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0001; const STATE_INITED: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0011; const STATE_RXGR_ACTIVE: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0100; const STATE_TXGR_ACTIVE: u32 = 0b0000_0000_0000_0000_0000_0000_0000_1000; const STATE_RXDMA_PRESENT: u32 = 0b0000_0000_0000_0000_0000_0000_0001_0000; const STATE_TXDMA_PRESENT: u32 = 0b0000_0000_0000_0000_0000_0000_0010_0000; const STATE_RXDMA_COMPLETE: u32 = 0b0000_0000_0000_0000_0000_0000_0100_0000; const STATE_RXDMA_MODE_MAXFRAME: u32 = 0b0000_0000_0000_0000_0000_0000_1000_0000; const STATE_RXGR_LEN_MASK: u32 = 0b1111_1111_1111_1111_0000_0000_0000_0000; struct BbqState { /// 0bGGGG_GGGG_GGGG_GGGG_xxxx_xxxx_MDTR_PCAI /// ^^--> 0b00: uninit, 0b01: initing, 0b11 init'd. /// ^----> 0b0: No Rx grant, 0b1: Rx grant active /// ^-----> 0b0: No Tx grant, 0b1: Tx grant active /// ^-------> 0b0: No Rx DMA present, 0b1: Rx DMA present /// ^--------> 0b0: No Tx DMA present, 0b1: Tx DMA present /// ^---------> 0b0: Rx DMA not complete, 0b1: Rx DMA complete /// ^----------> 0b0: RxMode "Efficiency", 0b1: RxMode "Max Frame" /// ^^^^_^^^^_^^^^_^^^^----------------------> 16-bit: RX Grant size state: AtomicU32, /// The "outgoing" bbqueue buffer /// /// Only valid when state is STATE_INITED + STATE_TXDMA_PRESENT. tx_queue: GroundedCell>, /// The "outgoing" transmit grant, which DMA will read from. /// /// Only valid when state is STATE_INITED + STATE_TXDMA_PRESENT + STATE_TXGR_ACTIVE. txgr: GroundedCell>>, /// The "outgoing" DMA channel. /// /// Only valid when state is STATE_INITED + STATE_TXDMA_PRESENT. txdma: GroundedCell>, /// The "outgoing" DMA request number. /// /// Only valid when state is STATE_INITED + STATE_TXDMA_PRESENT. txdma_num: AtomicU8, /// The "incoming" bbqueue buffer /// /// Only valid when state is STATE_INITED + STATE_RXDMA_PRESENT. rx_queue: GroundedCell>, /// The "incoming" receive grant, which DMA will write to. /// /// Only valid when state is STATE_INITED + STATE_RXDMA_PRESENT + STATE_RXGR_ACTIVE. rxgr: GroundedCell>>, /// The "incoming" DMA channel. /// /// Only valid when state is STATE_INITED + STATE_RXDMA_PRESENT. rxdma: GroundedCell>, /// The "incoming" DMA request number. /// /// Only valid when state is STATE_INITED + STATE_RXDMA_PRESENT. rxdma_num: AtomicU8, /// Waiter for the outgoing buffer to be flushed tx_flushed: WaitCell, } impl BbqState { const fn new() -> Self { Self { state: AtomicU32::new(0), tx_queue: GroundedCell::uninit(), rx_queue: GroundedCell::uninit(), rxgr: GroundedCell::uninit(), txgr: GroundedCell::uninit(), txdma: GroundedCell::uninit(), txdma_num: AtomicU8::new(0), rxdma: GroundedCell::uninit(), rxdma_num: AtomicU8::new(0), tx_flushed: WaitCell::new(), } } /// Attempt to move from the "uninit" state to the "initing" state. Returns an /// error if we are not in the "uninit" state. fn uninit_to_initing(&'static self) -> Result<(), BbqError> { self.state .compare_exchange(STATE_UNINIT, STATE_INITING, Ordering::AcqRel, Ordering::Acquire) .map(drop) .map_err(|_| BbqError::Busy) } /// Complete an active TX DMA transfer. Called from ISR context. /// /// After calling, the transmit half of the driver will be in the idle state. /// /// ## SAFETY /// /// * The HAL driver must be initialized /// * The TXDMA must be present /// * A write grant must be active /// * We must be in ISR context unsafe fn finalize_write(&'static self, info: &'static Info) { // SAFETY: With the function-level safety requirements met, we are free to modify // shared tx state. unsafe { // Load the active TX grant, taking it "by ownership" let txgr = self.txgr.get().read(); // Get the TX DMA, taking it by &mut ref let txdma = &mut *self.txdma.get(); // Stop the DMA info.regs().baud().modify(|w| w.set_tdmae(false)); txdma.disable_request(); txdma.clear_done(); // TODO: Some other way of ensuring the DMA is completely stopped? fence(Ordering::Acquire); // The max transfer length was the lesser of capacity / 4 or the max DMA transfer size // in a single transaction. This is because the `read()` used to create this grant may // be larger, if more bytes were available. let max_len = (&*self.tx_queue.get()).capacity() / 4; let xfer = txgr.len().min(max_len).min(DMA_MAX_TRANSFER_SIZE); // Release the number of transferred bytes, making them available to the user to reuse, // and waking the write waiter if there is one present (e.g. if we were previously full). txgr.release(xfer); } // Mark the TXGR as inactive, signifying "idle" self.state.fetch_and(!STATE_TXGR_ACTIVE, Ordering::AcqRel); } /// Complete an active RX DMA transfer. Called from ISR context. /// /// After calling, the receive half of the driver will be in the idle state. /// /// ## SAFETY /// /// * The HAL driver must be initialized /// * The RXDMA must be present /// * A read grant must be active /// * We must be in ISR context unsafe fn finalize_read(&'static self, info: &'static Info) { // SAFETY: With the function-level safety requirements met, we are free to modify // shared rx state. unsafe { // Load the active RX grant, taking it by ownership let rxgr = self.rxgr.get().read(); // Get the RX DMA, taking it by &mut ref let rxdma = &mut *self.rxdma.get(); // Stop the active DMA. // The DMA may NOT be done yet if this was an idle interrupt info.regs().baud().modify(|w| w.set_rdmae(false)); rxdma.disable_request(); rxdma.clear_done(); // Fence to ensure all DMA written bytes are complete, and we can see // any writes to the DADDR fence(Ordering::AcqRel); // Calculate the number of bytes written using the current write address of the // DMA channel, minus our starting address. let daddr = rxdma.daddr() as usize; let sstrt = rxgr.as_ptr() as usize; let ttl = daddr.wrapping_sub(sstrt).min(rxgr.len()); // Commit these bytes, making them visible to the user, and waking any pending // waiters if any (e.g. if we were previously empty) rxgr.commit(ttl); } // Mark the RXGR inactive, signifying idle self.state.fetch_and(!STATE_RXGR_ACTIVE, Ordering::AcqRel); } /// Attempt to start an active write transfer. Called from ISR context. /// /// Returns true if a transfer was started, and returns false if no transfer /// was started (e.g. the outgoing buffer is completely drained). /// /// ## SAFETY /// /// * The HAL driver must be initialized /// * The TXDMA must be present /// * A write grant must NOT be active /// * We must be in ISR context unsafe fn start_write_transfer(&'static self, info: &'static Info) -> bool { // Get the tx queue, by & ref // // SAFETY: TXDMA_PRESENT bit being enabled means the tx_queue has been initialized. // The tx_queue is safe to access in a shared manner after initialization. let tx_queue = unsafe { &*self.tx_queue.get() }; let Ok(rgr) = tx_queue.stream_consumer().read() else { // Nothing to do! return false; }; // SAFETY: With the function-level safety requirements met, we are free to modify // shared tx state. unsafe { // Take the TXDMA by &mut ref let txdma = &mut *self.txdma.get(); // Initialize the transfer from the bbqueue grant to DMA // // TODO: Most of this setup is redundant/repeated, we could save some effort // since most DMA transfer parameters are the same. let source = DmaRequest::from_number_unchecked(self.txdma_num.load(Ordering::Relaxed)); txdma.disable_request(); txdma.clear_done(); txdma.clear_interrupt(); txdma.set_request_source(source); let peri_addr = info.regs().data().as_ptr().cast::(); // NOTE: we limit the max transfer size to 1/4 the capacity for latency reasons, // so we can make buffer space available for further writing by the application // as soon as possible, as the buffer space is not made available until after // the transfer completes. let max_len = (&*self.tx_queue.get()).capacity() / 4; let len = rgr.len().min(max_len).min(DMA_MAX_TRANSFER_SIZE); if let Err(InvalidParameters) = txdma.setup_write_to_peripheral(&rgr[..len], peri_addr, false, TransferOptions::COMPLETE_INTERRUPT) { return false; } // Enable the DMA transfer info.regs().baud().modify(|w| w.set_tdmae(true)); txdma.enable_request(); // Store (by ownership) the outgoing read grant to the bbqueue state self.txgr.get().write(rgr); // Mark the TXGR as active, signifying the "transmitting" state self.state.fetch_or(STATE_TXGR_ACTIVE, Ordering::AcqRel); } // wait until the system is not reporting TC complete, to ensure we don't // immediately retrigger an interrupt. // // TODO: I'm not sure this actually ever happens, this is a defensive check while info.regs.stat().read().tc() == Tc::COMPLETE {} true } /// Attempt to start an active read transfer. Called from ISR context. /// /// Returns true if a transfer was started, and returns false if no transfer /// was started (e.g. the incoming buffer is completely full). /// /// ## SAFETY /// /// * The HAL driver must be initialized /// * The RXDMA must be present /// * A write grant must NOT be active /// * We must be in ISR context unsafe fn start_read_transfer(&'static self, info: &'static Info) -> bool { // SAFETY: RXDMA_PRESENT bit being enabled means the rx_queue has been initialized. // The rx_queue is safe to access in a shared manner after initialization. let rx_queue = unsafe { &*self.rx_queue.get() }; // Determine the size and kind of grant to request let state = self.state.load(Ordering::Relaxed); let len = (state >> 16) as usize; let is_max_frame = (state & STATE_RXDMA_MODE_MAXFRAME) != 0; let prod = rx_queue.stream_producer(); let grant_res = if is_max_frame { prod.grant_exact(len) } else { prod.grant_max_remaining(len) }; let Ok(mut wgr) = grant_res else { // If we can't get a grant, that's a problem. Return false to note we didn't // start one, and hope the user frees space soon. See the `read` method for // how read transfers are restarted in this case. return false; }; // SAFETY: With the function-level safety requirements met, we are free to modify // shared rx state. unsafe { // Initialize the transfer from the DMA to the bbqueue grant // // TODO: Most of this setup is redundant/repeated, we could save some effort // since most DMA transfer parameters are the same. let rxdma = &mut *self.rxdma.get(); let source = DmaRequest::from_number_unchecked(self.rxdma_num.load(Ordering::Relaxed)); rxdma.disable_request(); rxdma.clear_done(); rxdma.clear_interrupt(); rxdma.set_request_source(source); let peri_addr = info.regs().data().as_ptr().cast::(); if let Err(InvalidParameters) = rxdma.setup_read_from_peripheral(peri_addr, &mut wgr, false, TransferOptions::COMPLETE_INTERRUPT) { return false; } // Enable the DMA transfer info.regs().baud().modify(|w| w.set_rdmae(true)); rxdma.enable_request(); // Store (by ownership) the incoming write grant to the bbqueue state self.rxgr.get().write(wgr); // Mark the RXGR as active, signifying the "receiving" state self.state.fetch_or(STATE_RXGR_ACTIVE, Ordering::AcqRel); } true } } #[allow(private_bounds, private_interfaces)] pub trait BbqInstance: Instance { /// The BBQ specific state storage fn bbq_state() -> &'static BbqState; /// A callback for the DMA handler to call that marks RXDMA as complete and /// pends the LPUART interrupt for further processing. fn dma_rx_complete_cb(); } macro_rules! impl_instance { ($($n:expr);* $(;)?) => { $( paste!{ #[allow(private_interfaces)] impl BbqInstance for crate::peripherals::[] { fn bbq_state() -> &'static BbqState { static STATE: BbqState = BbqState::new(); &STATE } fn dma_rx_complete_cb() { let state = Self::bbq_state(); // Mark the DMA as complete state.state.fetch_or(STATE_RXDMA_COMPLETE, Ordering::AcqRel); // Pend the UART interrupt to handle the switchover Self::Interrupt::pend(); } } } )* }; } impl_instance!(0; 1; 2; 3; 4); #[cfg(feature = "mcxa5xx")] impl_instance!(5); // Basically the on_interrupt handler, but as a free function so it doesn't get // monomorphized. // // SAFETY: Should only be called by the `on_interrupt` function in ISR context, with // the shared BbqState properly initialized in the INITED state unsafe fn handler(info: &'static Info, state: &'static BbqState) { let regs = info.regs(); let ctrl = regs.ctrl().read(); let stat = regs.stat().read(); // Just clear any errors - TODO, signal these to the consumer? // For now, we just clear + discard errors if they occur. let or = stat.or(); let pf = stat.pf(); let fe = stat.fe(); let nf = stat.nf(); let idle = stat.idle(); regs.stat().modify(|w| { w.set_or(or); w.set_pf(pf); w.set_fe(fe); w.set_nf(nf); w.set_idle(idle); }); // // RX state machine // // Check DMA complete or idle interrupt occurred - we need to stop // the current RX transfer in either case. let pre_clear = state.state.fetch_and(!STATE_RXDMA_COMPLETE, Ordering::AcqRel); // SAFETY NOTE: The RXDMA_PRESENT bit is used to mediate whether the interrupt should // act the shared RX data. This is used by functions like `teardown` to disable interrupt // access to shared data when tearing down. let rx_present = (pre_clear & STATE_RXDMA_PRESENT) != 0; if rx_present { let rx_active = (pre_clear & STATE_RXGR_ACTIVE) != 0; let dma_complete = (pre_clear & STATE_RXDMA_COMPLETE) != 0; if rx_active && (idle || dma_complete) { // State change, move from Receiving -> Idle // // SAFETY: The HAL driver is initialized, we checked that RXDMA_PRESENT is set, we // checked that RXGR_ACTIVE is set, we are in ISR context unsafe { state.finalize_read(info); } } // If we are now idle, attempt to "reload" the transfer and being receiving again ASAP. // Only do this if RXDMA is present. We re-load from state to ensure we see when // `finalize_read` just cleared the bit. let rx_idle = (state.state.load(Ordering::Acquire) & STATE_RXGR_ACTIVE) == 0; if rx_idle { // Either Idle -> Receiving or Idle -> Idle // // SAFETY: The HAL driver is initialized, we checked that RXDMA_PRESENT is set, we // checked there isn't a write grant active, and we are in ISR context. unsafe { let started = state.start_read_transfer(info); // Enable ILIE if we started a transfer, otherwise (keep) disabled. // ILIE - Idle Line Interrupt Enable regs.ctrl().modify(|w| w.set_ilie(started)); } } } // // TX state machine // // SAFETY NOTE: The TXDMA_PRESENT bit is used to mediate whether the interrupt should // act the shared TX data. This is used by functions like `teardown` to disable interrupt // access to shared data when tearing down. let tx_state = state.state.load(Ordering::Acquire); let tx_present = (tx_state & STATE_TXDMA_PRESENT) != 0; if tx_present { // Handle TX data - TCIE is only enabled if we are transmitting, and we only // check that the outgoing transfer is complete. In the future, we might // try to do this a bit earlier if the DMA completes but we haven't yet // drained the TX fifo yet. let txie_set = ctrl.tcie(); let tc_complete = regs.stat().read().tc() == Tc::COMPLETE; let txgr_present = (tx_state & STATE_TXGR_ACTIVE) != 0; let tx_did_finish = txie_set && tc_complete && txgr_present; if tx_did_finish { // State change, move from Transmitting -> Idle // // SAFETY: The driver has been initialized, we've checked TXDMA_PRESENT is set, // we've checked TXGR_ACTIVE is set, we are in ISR context. unsafe { state.finalize_write(info); } } // If we are now idle, attempt to "reload" the transfer and begin transmitting again. // Only do this if TXDMA is present. let tx_idle = (state.state.load(Ordering::Acquire) & STATE_TXGR_ACTIVE) == 0; if tx_idle { // Either Idle -> Transmitting or Idle -> Idle // // SAFETY: The driver has been initialized, we've checked TXDMA_PRESENT is set, // we've checked TXGR_ACTIVE is NOT set, we are in ISR context. unsafe { let started = state.start_write_transfer(info); // Enable tcie if we started a transfer, otherwise (keep) disabled. // TCIE - Transfer Complete Interrupt Enable regs.ctrl().modify(|w| w.set_tcie(started)); // Did we go from "transmitting" to "idle" in this ISR? If so, wake any "flush" waiters. if tx_did_finish && !started { state.tx_flushed.wake(); } } } } } impl Handler for BbqInterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); let info = T::info(); let state = T::bbq_state(); // SAFETY: Interrupts are only enabled when state is valid, we are calling the handler // from ISR context. unsafe { handler(info, state); } } } use crate::gpio::SealedPin; fn any_as_tx(pin: &Peri<'_, AnyPin>, mux: crate::pac::port::vals::Mux) { pin.set_pull(crate::gpio::Pull::Disabled); pin.set_slew_rate(crate::gpio::SlewRate::Fast.into()); pin.set_drive_strength(crate::gpio::DriveStrength::Normal.into()); pin.set_function(mux); pin.set_enable_input_buffer(false); } fn any_as_rx(pin: &Peri<'_, AnyPin>, mux: crate::pac::port::vals::Mux) { pin.set_pull(crate::gpio::Pull::Disabled); pin.set_function(mux); pin.set_enable_input_buffer(true); } ================================================ FILE: embassy-mcxa/src/lpuart/blocking.rs ================================================ use embassy_hal_internal::Peri; use super::*; use crate::pac::lpuart::vals::{Tc, Tdre}; /// Blocking mode. pub struct Blocking; impl sealed::Sealed for Blocking {} impl Mode for Blocking {} impl<'a> Lpuart<'a, Blocking> { /// Create a new blocking LPUART instance with RX/TX pins. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, config: Config, ) -> Result { // Configure the pins for LPUART usage tx_pin.as_tx(); rx_pin.as_rx(); let wg = Self::init::(true, true, false, false, config)?; Ok(Self { tx: LpuartTx::new_inner::(tx_pin.into(), None, Blocking, wg.clone()), rx: LpuartRx::new_inner::(rx_pin.into(), None, Blocking, wg), }) } /// Create a new blocking LPUART instance with RX, TX and RTS/CTS flow control pins. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking_with_rtscts( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, cts_pin: Peri<'a, impl CtsPin>, rts_pin: Peri<'a, impl RtsPin>, config: Config, ) -> Result { // Configure the pins for LPUART usage rx_pin.as_rx(); tx_pin.as_tx(); rts_pin.as_rts(); cts_pin.as_cts(); let wg = Self::init::(true, true, true, true, config)?; Ok(Self { rx: LpuartRx::new_inner::(rx_pin.into(), Some(rts_pin.into()), Blocking, wg.clone()), tx: LpuartTx::new_inner::(tx_pin.into(), Some(cts_pin.into()), Blocking, wg), }) } /// Read data from LPUART RX blocking execution until the buffer is filled pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buf) } /// Read data from LPUART RX without blocking pub fn read(&mut self, buf: &mut [u8]) -> Result<(), Error> { self.rx.read(buf) } /// Write data to LPUART TX blocking execution until all data is sent pub fn blocking_write(&mut self, buf: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buf) } pub fn write_byte(&mut self, byte: u8) -> Result<(), Error> { self.tx.write_byte(byte) } pub fn read_byte_blocking(&mut self) -> u8 { loop { if let Ok(b) = self.rx.read_byte() { return b; } } } pub fn write_str_blocking(&mut self, buf: &str) { self.tx.write_str_blocking(buf); } /// Write data to LPUART TX without blocking pub fn write(&mut self, buf: &[u8]) -> Result<(), Error> { self.tx.write(buf) } /// Flush LPUART TX blocking execution until all data has been transmitted pub fn blocking_flush(&mut self) -> Result<(), Error> { self.tx.blocking_flush() } /// Flush LPUART TX without blocking pub fn flush(&mut self) -> Result<(), Error> { self.tx.flush() } } impl<'a> LpuartTx<'a, Blocking> { /// Create a new blocking LPUART transmitter instance. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, config: Config, ) -> Result { // Configure the pins for LPUART usage tx_pin.as_tx(); let wg = Lpuart::::init::(true, false, false, false, config)?; Ok(Self::new_inner::(tx_pin.into(), None, Blocking, wg)) } /// Create a new blocking LPUART transmitter instance with CTS flow control. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking_with_cts( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, cts_pin: Peri<'a, impl CtsPin>, config: Config, ) -> Result { tx_pin.as_tx(); cts_pin.as_cts(); let wg = Lpuart::::init::(true, false, true, false, config)?; Ok(Self::new_inner::(tx_pin.into(), Some(cts_pin.into()), Blocking, wg)) } fn write_byte_internal(&mut self, byte: u8) -> Result<(), Error> { self.info.regs().data().modify(|w| w.0 = u32::from(byte)); Ok(()) } fn blocking_write_byte(&mut self, byte: u8) -> Result<(), Error> { while self.info.regs().stat().read().tdre() == Tdre::TXDATA {} self.write_byte_internal(byte) } fn write_byte(&mut self, byte: u8) -> Result<(), Error> { if self.info.regs().stat().read().tdre() == Tdre::TXDATA { Err(Error::TxFifoFull) } else { self.write_byte_internal(byte) } } /// Write data to LPUART TX blocking execution until all data is sent. pub fn blocking_write(&mut self, buf: &[u8]) -> Result<(), Error> { for x in buf { self.blocking_write_byte(*x)?; } Ok(()) } pub fn write_str_blocking(&mut self, buf: &str) { let _ = self.blocking_write(buf.as_bytes()); } /// Write data to LPUART TX without blocking. pub fn write(&mut self, buf: &[u8]) -> Result<(), Error> { for x in buf { self.write_byte(*x)?; } Ok(()) } /// Flush LPUART TX blocking execution until all data has been transmitted. pub fn blocking_flush(&mut self) -> Result<(), Error> { while self.info.regs().water().read().txcount() != 0 { // Wait for TX FIFO to drain } // Wait for last character to shift out while self.info.regs().stat().read().tc() == Tc::ACTIVE { // Wait for transmission to complete } Ok(()) } /// Flush LPUART TX. pub fn flush(&mut self) -> Result<(), Error> { // Check if TX FIFO is empty if self.info.regs().water().read().txcount() != 0 { return Err(Error::TxBusy); } // Check if transmission is complete if self.info.regs().stat().read().tc() == Tc::ACTIVE { return Err(Error::TxBusy); } Ok(()) } } impl<'a> LpuartRx<'a, Blocking> { /// Create a new blocking LPUART Receiver instance. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking( _inner: Peri<'a, T>, rx_pin: Peri<'a, impl RxPin>, config: Config, ) -> Result { rx_pin.as_rx(); let wg = Lpuart::::init::(false, true, false, false, config)?; Ok(Self::new_inner::(rx_pin.into(), None, Blocking, wg)) } /// Create a new blocking LPUART Receiver instance with RTS flow control. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_blocking_with_rts( _inner: Peri<'a, T>, rx_pin: Peri<'a, impl RxPin>, rts_pin: Peri<'a, impl RtsPin>, config: Config, ) -> Result { rx_pin.as_rx(); rts_pin.as_rts(); let wg = Lpuart::::init::(false, true, false, true, config)?; Ok(Self::new_inner::(rx_pin.into(), Some(rts_pin.into()), Blocking, wg)) } fn read_byte_internal(&mut self) -> Result { Ok((self.info.regs().data().read().0 & 0xFF) as u8) } fn read_byte(&mut self) -> Result { check_and_clear_rx_errors(self.info)?; if !has_rx_data_pending(self.info) { return Err(Error::RxFifoEmpty); } self.read_byte_internal() } fn blocking_read_byte(&mut self) -> Result { loop { if has_rx_data_pending(self.info) { return self.read_byte_internal(); } check_and_clear_rx_errors(self.info)?; } } /// Read data from LPUART RX without blocking. pub fn read(&mut self, buf: &mut [u8]) -> Result<(), Error> { for byte in buf.iter_mut() { *byte = self.read_byte()?; } Ok(()) } /// Read data from LPUART RX blocking execution until the buffer is filled. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<(), Error> { for byte in buf.iter_mut() { *byte = self.blocking_read_byte()?; } Ok(()) } } impl embedded_hal_02::serial::Read for LpuartRx<'_, Blocking> { type Error = Error; fn read(&mut self) -> core::result::Result> { let mut buf = [0; 1]; match self.read(&mut buf) { Ok(_) => Ok(buf[0]), Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_02::serial::Write for LpuartTx<'_, Blocking> { type Error = Error; fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { match self.write(&[word]) { Ok(_) => Ok(()), Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } fn flush(&mut self) -> core::result::Result<(), nb::Error> { match self.flush() { Ok(_) => Ok(()), Err(Error::TxBusy) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_02::blocking::serial::Write for LpuartTx<'_, Blocking> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { self.blocking_write(buffer) } fn bflush(&mut self) -> core::result::Result<(), Self::Error> { self.blocking_flush() } } impl embedded_hal_02::serial::Read for Lpuart<'_, Blocking> { type Error = Error; fn read(&mut self) -> core::result::Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } impl embedded_hal_02::serial::Write for Lpuart<'_, Blocking> { type Error = Error; fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { embedded_hal_02::serial::Write::write(&mut self.tx, word) } fn flush(&mut self) -> core::result::Result<(), nb::Error> { embedded_hal_02::serial::Write::flush(&mut self.tx) } } impl embedded_hal_02::blocking::serial::Write for Lpuart<'_, Blocking> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { self.blocking_write(buffer) } fn bflush(&mut self) -> core::result::Result<(), Self::Error> { self.blocking_flush() } } impl embedded_hal_nb::serial::Read for LpuartRx<'_, Blocking> { fn read(&mut self) -> nb::Result { let mut buf = [0; 1]; match self.read(&mut buf) { Ok(_) => Ok(buf[0]), Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl embedded_hal_nb::serial::Write for LpuartTx<'_, Blocking> { fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { match self.write(&[word]) { Ok(_) => Ok(()), Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } fn flush(&mut self) -> nb::Result<(), Self::Error> { match self.flush() { Ok(_) => Ok(()), Err(Error::TxBusy) => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(e)), } } } impl core::fmt::Write for LpuartTx<'_, Blocking> { fn write_str(&mut self, s: &str) -> core::fmt::Result { self.blocking_write(s.as_bytes()).map_err(|_| core::fmt::Error) } } impl embedded_hal_nb::serial::Read for Lpuart<'_, Blocking> { fn read(&mut self) -> nb::Result { embedded_hal_nb::serial::Read::read(&mut self.rx) } } impl embedded_hal_nb::serial::Write for Lpuart<'_, Blocking> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { embedded_hal_nb::serial::Write::write(&mut self.tx, char) } fn flush(&mut self) -> nb::Result<(), Self::Error> { embedded_hal_nb::serial::Write::flush(&mut self.tx) } } impl embedded_io::Read for LpuartRx<'_, Blocking> { fn read(&mut self, buf: &mut [u8]) -> core::result::Result { self.blocking_read(buf).map(|_| buf.len()) } } impl embedded_io::Write for LpuartTx<'_, Blocking> { fn write(&mut self, buf: &[u8]) -> core::result::Result { self.blocking_write(buf).map(|_| buf.len()) } fn flush(&mut self) -> core::result::Result<(), Self::Error> { self.blocking_flush() } } impl embedded_io::Read for Lpuart<'_, Blocking> { fn read(&mut self, buf: &mut [u8]) -> core::result::Result { embedded_io::Read::read(&mut self.rx, buf) } } impl embedded_io::Write for Lpuart<'_, Blocking> { fn write(&mut self, buf: &[u8]) -> core::result::Result { embedded_io::Write::write(&mut self.tx, buf) } fn flush(&mut self) -> core::result::Result<(), Self::Error> { embedded_io::Write::flush(&mut self.tx) } } ================================================ FILE: embassy-mcxa/src/lpuart/buffered.rs ================================================ use core::marker::PhantomData; use embassy_hal_internal::Peri; use super::*; use crate::clocks::WakeGuard; use crate::interrupt::typelevel::Interrupt; use crate::interrupt::{self}; /// Async buffered mode where the bytes are pumped by the interrupt handler. pub struct Buffered; impl sealed::Sealed for Buffered {} impl Mode for Buffered {} impl<'a> Lpuart<'a, Buffered> { /// Common initialization logic fn init_buffered( tx_buffer: Option<&'a mut [u8]>, rx_buffer: Option<&'a mut [u8]>, config: Config, enable_tx: bool, enable_rx: bool, enable_rts: bool, enable_cts: bool, ) -> Result, Error> { // Initialize buffers if let Some(tx_buffer) = tx_buffer { if tx_buffer.is_empty() { return Err(Error::InvalidArgument); } unsafe { T::state().tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) }; } if let Some(rx_buffer) = rx_buffer { if rx_buffer.is_empty() { return Err(Error::InvalidArgument); } unsafe { T::state().rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) }; } // Enable clocks and initialize hardware let conf = LpuartConfig { power: config.power, source: config.source, div: config.div, instance: T::CLOCK_INSTANCE, }; let parts = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; Self::init::(enable_tx, enable_rx, enable_cts, enable_rts, config)?; // Enable interrupts for buffered operation cortex_m::interrupt::free(|_| { T::info().regs().ctrl().modify(|w| { w.set_rie(true); // RX interrupt w.set_orie(true); // Overrun interrupt w.set_peie(true); // Parity error interrupt w.set_feie(true); // Framing error interrupt w.set_neie(true); // Noise error interrupt }); }); // Enable interrupt T::Interrupt::unpend(); unsafe { T::Interrupt::enable(); } Ok(parts.wake_guard) } /// Helper for full-duplex initialization fn new_inner_buffered( tx_pin: Peri<'a, AnyPin>, rx_pin: Peri<'a, AnyPin>, rts_pin: Option>, cts_pin: Option>, tx_buffer: &'a mut [u8], rx_buffer: &'a mut [u8], config: Config, ) -> Result { let wg = Self::init_buffered::( Some(tx_buffer), Some(rx_buffer), config, true, true, rts_pin.is_some(), cts_pin.is_some(), )?; Ok(Self { tx: LpuartTx::new_inner::(tx_pin, cts_pin, Buffered, wg.clone()), rx: LpuartRx::new_inner::(rx_pin, rts_pin, Buffered, wg), }) } /// Create a new full duplex buffered LPUART. /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `Lpuart` if `rx_buffer` or `tx_buffer` are not /// `'static`. This will cause memory corruption. pub fn new_buffered( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_buffer: &'a mut [u8], rx_buffer: &'a mut [u8], config: Config, ) -> Result { tx_pin.as_tx(); rx_pin.as_rx(); Self::new_inner_buffered::(tx_pin.into(), rx_pin.into(), None, None, tx_buffer, rx_buffer, config) } /// Create a new buffered LPUART instance with RTS/CTS flow control. /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `Lpuart` if `rx_buffer` or `tx_buffer` are not /// `'static`. This will cause memory corruption. pub fn new_buffered_with_rtscts( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, rts_pin: Peri<'a, impl RtsPin>, cts_pin: Peri<'a, impl CtsPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_buffer: &'a mut [u8], rx_buffer: &'a mut [u8], config: Config, ) -> Result { tx_pin.as_tx(); rx_pin.as_rx(); rts_pin.as_rts(); cts_pin.as_cts(); Self::new_inner_buffered::( tx_pin.into(), rx_pin.into(), Some(rts_pin.into()), Some(cts_pin.into()), tx_buffer, rx_buffer, config, ) } /// Create a new buffered LPUART with only RTS flow control (RX flow control). /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `Lpuart` if `rx_buffer` or `tx_buffer` are not /// `'static`. This will cause memory corruption. pub fn new_buffered_with_rts( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, rts_pin: Peri<'a, impl RtsPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_buffer: &'a mut [u8], rx_buffer: &'a mut [u8], config: Config, ) -> Result { tx_pin.as_tx(); rx_pin.as_rx(); rts_pin.as_rts(); Self::new_inner_buffered::( tx_pin.into(), rx_pin.into(), Some(rts_pin.into()), None, tx_buffer, rx_buffer, config, ) } /// Create a new buffered LPUART with only CTS flow control (TX flow control). /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `Lpuart` if `rx_buffer` or `tx_buffer` are not /// `'static`. This will cause memory corruption. pub fn new_buffered_with_cts( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, cts_pin: Peri<'a, impl CtsPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_buffer: &'a mut [u8], rx_buffer: &'a mut [u8], config: Config, ) -> Result { tx_pin.as_tx(); rx_pin.as_rx(); cts_pin.as_cts(); Self::new_inner_buffered::( tx_pin.into(), rx_pin.into(), None, Some(cts_pin.into()), tx_buffer, rx_buffer, config, ) } } impl<'a> LpuartTx<'a, Buffered> { /// Helper for TX-only initialization fn new_inner_buffered( tx_pin: Peri<'a, AnyPin>, cts_pin: Option>, tx_buffer: &'a mut [u8], config: Config, ) -> Result { let wg = Lpuart::<'a, Buffered>::init_buffered::( Some(tx_buffer), None, config, true, false, false, cts_pin.is_some(), )?; Ok(Self::new_inner::(tx_pin, cts_pin, Buffered, wg)) } /// Create a new TX-only LPUART. /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `LpuartTx` if `tx_buffer` is not `'static`. /// This will potentially send "garbage" data via the UART. pub fn new( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_buffer: &'a mut [u8], config: Config, ) -> Result { tx_pin.as_tx(); let res = Self::new_inner_buffered::(tx_pin.into(), None, tx_buffer, config)?; Ok(res) } /// Create a new TX-only buffered LPUART with CTS flow control. /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `LpuartTx` if `tx_buffer` is not `'static`. /// This will potentially send "garbage" data via the UART. pub fn new_with_cts( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, cts_pin: Peri<'a, impl CtsPin>, _irq: impl interrupt::typelevel::Binding> + 'a, tx_buffer: &'a mut [u8], config: Config, ) -> Result { tx_pin.as_tx(); cts_pin.as_cts(); let res = Self::new_inner_buffered::(tx_pin.into(), Some(cts_pin.into()), tx_buffer, config)?; // Enable interrupt T::Interrupt::unpend(); unsafe { T::Interrupt::enable(); } Ok(res) } /// Write data asynchronously pub async fn write(&mut self, buf: &[u8]) -> Result { if buf.is_empty() { return Ok(0); } // Wait for space in the buffer self.state.tx_waker.wait_for(|| !self.state.tx_buf.is_full()).await?; // We now know there is space, so do a normal try_write Ok(self.try_write(buf)) } /// Flush the TX buffer and wait for transmission to complete pub async fn flush(&mut self) -> Result<(), Error> { // Wait for TX buffer to empty and transmission to complete Ok(self .state .tx_waker .wait_for(|| { let tx_empty = self.state.tx_buf.is_empty(); let fifo_empty = self.info.regs().water().read().txcount() == 0; let tc_complete = self.info.regs().stat().read().tc() == Tc::COMPLETE; tx_empty && fifo_empty && tc_complete }) .await?) } /// Try to write without blocking /// /// May return 0 if the provided buf is zero, or there are no bytes available pub fn try_write(&mut self, buf: &[u8]) -> usize { if buf.is_empty() { return 0; } // SAFETY: Ring buffer is initialized on `new`, there can only be one `LpuartTx` // live at once, exclusive access is guaranteed by `&mut self`. let mut writer = unsafe { self.state.tx_buf.writer() }; let starting_len = buf.len(); let mut window = buf; // This will usually run once, and at most twice, if we are in a wrap-around // condition. loop { // Get destination let dst = writer.push_slice(); // Copy what is possible let to_copy = dst.len().min(window.len()); let (now, later) = window.split_at(to_copy); dst[..to_copy].copy_from_slice(now); // Update the "to send" window to only contain the remaining unsent bytes window = later; // Update the ring buffer with the pushed bytes writer.push_done(to_copy); // If the copy is complete, or the buffer is full, we are done copying if to_copy == 0 || window.is_empty() { break; } } let written = starting_len - window.len(); if written > 0 { // Enable TX interrupt to start transmission cortex_m::interrupt::free(|_| { self.info.regs().ctrl().modify(|w| w.set_tie(true)); }); } written } } impl<'a> LpuartRx<'a, Buffered> { /// Helper for RX-only initialization fn new_inner_buffered( rx_pin: Peri<'a, AnyPin>, rts_pin: Option>, rx_buffer: &'a mut [u8], config: Config, ) -> Result { let wg = Lpuart::<'a, Buffered>::init_buffered::( None, Some(rx_buffer), config, false, true, rts_pin.is_some(), false, )?; Ok(Self::new_inner::(rx_pin, rts_pin, Buffered, wg)) } /// Create a new RX-only buffered LPUART. /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `LpuartRx` if `rx_buffer` is not `'static`. /// This will cause memory corruption. pub fn new( _inner: Peri<'a, T>, rx_pin: Peri<'a, impl RxPin>, _irq: impl interrupt::typelevel::Binding> + 'a, rx_buffer: &'a mut [u8], config: Config, ) -> Result { rx_pin.as_rx(); let res = Self::new_inner_buffered::(rx_pin.into(), None, rx_buffer, config)?; // Enable interrupt T::Interrupt::unpend(); unsafe { T::Interrupt::enable(); } Ok(res) } /// Create a new RX-only buffered LPUART with RTS flow control. /// /// Any external pin will be placed into Disabled state upon Drop. /// /// ## SAFETY /// /// You must NOT call `core::mem::forget` on `LpuartRx` if `rx_buffer` is not `'static`. /// This will cause memory corruption. pub fn new_with_rts( _inner: Peri<'a, T>, rx_pin: Peri<'a, impl RxPin>, rts_pin: Peri<'a, impl RtsPin>, _irq: impl interrupt::typelevel::Binding> + 'a, rx_buffer: &'a mut [u8], config: Config, ) -> Result { rx_pin.as_rx(); rts_pin.as_rts(); let res = Self::new_inner_buffered::(rx_pin.into(), Some(rts_pin.into()), rx_buffer, config)?; // Enable interrupt T::Interrupt::unpend(); unsafe { T::Interrupt::enable(); } Ok(res) } /// Read data asynchronously pub async fn read(&mut self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); } // Wait for some data to be available self.state.rx_waker.wait_for(|| !self.state.rx_buf.is_empty()).await?; // Now do a normal try_read, we know it will return a non-zero amount Ok(self.try_read(buf)) } /// Try to read without blocking /// /// May return zero bytes if none are available, or the provided buffer is /// of zero length. pub fn try_read(&mut self, buf: &mut [u8]) -> usize { if buf.is_empty() { return 0; } // SAFETY: Ring buffer is initialized on `new`, there can only be one `LpuartRx` // live at once, exclusive access is guaranteed by `&mut self`. let mut reader = unsafe { self.state.rx_buf.reader() }; let starting_len = buf.len(); let mut window = buf; // This will usually run once, and at most twice, if we are in a wrap-around // condition. loop { // Get source amount let src = reader.pop_slice(); // Determine the amount to copy, do so let to_copy = src.len().min(window.len()); let (now, later) = window.split_at_mut(to_copy); now.copy_from_slice(&src[..to_copy]); // Tell the ring buffer the space is now free reader.pop_done(to_copy); // The "to recv" window is the amount we didn't just fill window = later; // If we copied nothing or there are no bytes left to be copied, // then we are done if to_copy == 0 || window.is_empty() { break; } } starting_len - window.len() } } /// Buffered UART interrupt handler. pub struct BufferedInterruptHandler { _phantom: PhantomData, } impl crate::interrupt::typelevel::Handler for BufferedInterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); let regs = T::info().regs(); let state = T::state(); let ctrl = regs.ctrl().read(); let stat = regs.stat().read(); let param = regs.param().read(); let has_rx_fifo = param.rxfifo() > 0; let has_tx_fifo = param.txfifo() > 0; // Handle overrun error if stat.or() { regs.stat().write(|w| w.set_or(true)); T::PERF_INT_WAKE_INCR(); state.rx_waker.wake(); } // Clear other error flags if stat.pf() { regs.stat().write(|w| w.set_pf(true)); } if stat.fe() { regs.stat().write(|w| w.set_fe(true)); } if stat.nf() { regs.stat().write(|w| w.set_nf(true)); } // Handle RX data if ctrl.rie() && (has_rx_data_pending(T::info()) || stat.idle()) { let mut pushed_any = false; let mut writer = unsafe { state.rx_buf.writer() }; if has_rx_fifo { // Read from FIFO as long as there is data available and // somewhere to put it while regs.water().read().rxcount() > 0 && !state.rx_buf.is_full() { let byte = regs.data().read().0 as u8; writer.push_one(byte); pushed_any = true; } } else { // Read single byte if possible if regs.stat().read().rdrf() && !state.rx_buf.is_full() { let byte = (regs.data().read().0 & 0xFF) as u8; writer.push_one(byte); pushed_any = true; } } if pushed_any { T::PERF_INT_WAKE_INCR(); state.rx_waker.wake(); } // Clear idle flag if set if stat.idle() { regs.stat().write(|w| w.set_idle(true)); } } // Handle TX data if ctrl.tie() { let mut sent_any = false; let mut reader = unsafe { state.tx_buf.reader() }; let to_pop = if has_tx_fifo { // tx fifo size is 2^param.txfifo, we want to pop enough to fill // the fifo, minus whatever is in there now. (1 << param.txfifo()) - regs.water().read().txcount() } else if regs.stat().read().tdre() != Tdre::TXDATA { 1 } else { 0 }; // Send data while TX buffer is ready and we have data for _ in 0..to_pop { if let Some(byte) = reader.pop_one() { regs.data().write(|w| w.0 = u32::from(byte)); sent_any = true; } else { // No more data to send break; } } if sent_any { T::PERF_INT_WAKE_INCR(); state.tx_waker.wake(); } // If buffer is empty, switch to TC interrupt or disable if state.tx_buf.is_empty() { cortex_m::interrupt::free(|_| { regs.ctrl().modify(|w| { w.set_tie(false); w.set_tcie(true); }); }); } } // Handle transmission complete if ctrl.tcie() && regs.stat().read().tc() == Tc::COMPLETE { T::PERF_INT_WAKE_INCR(); state.tx_waker.wake(); // Disable TC interrupt cortex_m::interrupt::free(|_| { regs.ctrl().modify(|w| w.set_tcie(false)); }); } } } impl embedded_io_async::Write for LpuartTx<'_, Buffered> { async fn write(&mut self, buf: &[u8]) -> core::result::Result { self.write(buf).await } async fn flush(&mut self) -> core::result::Result<(), Self::Error> { self.flush().await } } impl embedded_io_async::Read for LpuartRx<'_, Buffered> { async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { self.read(buf).await } } impl embedded_io_async::Write for Lpuart<'_, Buffered> { async fn write(&mut self, buf: &[u8]) -> core::result::Result { self.tx.write(buf).await } async fn flush(&mut self) -> core::result::Result<(), Self::Error> { self.tx.flush().await } } impl embedded_io_async::Read for Lpuart<'_, Buffered> { async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { self.rx.read(buf).await } } ================================================ FILE: embassy-mcxa/src/lpuart/dma.rs ================================================ use core::future::Future; use embassy_hal_internal::Peri; use super::*; use crate::dma::{ Channel, DMA_MAX_TRANSFER_SIZE, DmaChannel, DmaRequest, InvalidParameters, RingBuffer, TransferOptions, }; use crate::gpio::AnyPin; use crate::pac::lpuart::vals::{Tc, Tdre}; /// DMA mode. pub struct Dma<'d> { dma: DmaChannel<'d>, request: DmaRequest, } impl sealed::Sealed for Dma<'_> {} impl Mode for Dma<'_> {} /// Lpuart RX driver with ring-buffered DMA support. pub struct RingBufferedLpuartRx<'peri, 'ring> { ring: RingBuffer<'peri, 'ring, u8>, } struct TxDmaGuard<'a> { dma: DmaChannel<'a>, info: &'static Info, } impl<'a> TxDmaGuard<'a> { fn new(dma: DmaChannel<'a>, info: &'static Info) -> Self { Self { dma, info } } /// Complete the transfer normally (don't abort on drop). fn complete(self) { // Cleanup self.info.regs().baud().modify(|w| w.set_tdmae(false)); unsafe { self.dma.disable_request(); self.dma.clear_done(); } // Don't run drop since we've cleaned up core::mem::forget(self); } } impl Drop for TxDmaGuard<'_> { fn drop(&mut self) { // Abort the DMA transfer if still running unsafe { self.dma.disable_request(); self.dma.clear_done(); self.dma.clear_interrupt(); } // Disable UART TX DMA request self.info.regs().baud().modify(|w| w.set_tdmae(false)); } } /// Guard struct for RX DMA transfers. struct RxDmaGuard<'a> { dma: DmaChannel<'a>, info: &'static Info, } impl<'a> RxDmaGuard<'a> { fn new(dma: DmaChannel<'a>, info: &'static Info) -> Self { Self { dma, info } } /// Complete the transfer normally (don't abort on drop). fn complete(self) { // Ensure DMA writes are visible to CPU cortex_m::asm::dsb(); // Cleanup self.info.regs().baud().modify(|w| w.set_rdmae(false)); unsafe { self.dma.disable_request(); self.dma.clear_done(); } // Don't run drop since we've cleaned up core::mem::forget(self); } } impl Drop for RxDmaGuard<'_> { fn drop(&mut self) { // Abort the DMA transfer if still running unsafe { self.dma.disable_request(); self.dma.clear_done(); self.dma.clear_interrupt(); } // Disable UART RX DMA request self.info.regs().baud().modify(|w| w.set_rdmae(false)); } } impl<'a> LpuartTx<'a, Dma<'a>> { /// Create a new LPUART TX driver with DMA support. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_async_with_dma( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, tx_dma_ch: Peri<'a, impl Channel>, config: Config, ) -> Result { tx_pin.as_tx(); let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); // Initialize LPUART with TX enabled, RX disabled, no flow control let wg = Lpuart::>::init::(true, false, false, false, config)?; // Enable interrupt let dma = DmaChannel::new(tx_dma_ch); dma.enable_interrupt(); Ok(Self::new_inner::( tx_pin, None, Dma { dma, request: T::TX_DMA_REQUEST, }, wg, )) } /// Write data using DMA. /// /// This configures the DMA channel for a memory-to-peripheral transfer /// and waits for completion asynchronously. Large buffers are automatically /// split into chunks that fit within the DMA transfer limit. /// /// The DMA request source is automatically derived from the LPUART instance type. /// /// # Safety /// /// If the returned future is dropped before completion (e.g., due to a timeout), /// the DMA transfer is automatically aborted to prevent use-after-free. /// /// # Arguments /// * `buf` - Data buffer to transmit pub async fn write(&mut self, buf: &[u8]) -> Result { if buf.is_empty() { return Ok(0); } let mut total = 0; for chunk in buf.chunks(DMA_MAX_TRANSFER_SIZE) { total += self.write_dma_inner(chunk).await?; } Ok(total) } /// Internal helper to write a single chunk (max 0x7FFF bytes) using DMA. async fn write_dma_inner(&mut self, buf: &[u8]) -> Result { let len = buf.len(); let peri_addr = self.info.regs().data().as_ptr() as *mut u8; let dma = &mut self.mode.dma; unsafe { // Clean up channel state dma.disable_request(); dma.clear_done(); dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) dma.set_request_source(self.mode.request); // Configure TCD for memory-to-peripheral transfer dma.setup_write_to_peripheral(buf, peri_addr, false, TransferOptions::COMPLETE_INTERRUPT)?; // Enable UART TX DMA request self.info.regs().baud().modify(|w| w.set_tdmae(true)); // Enable DMA channel request dma.enable_request(); } // Create guard that will abort DMA if this future is dropped let guard = TxDmaGuard::new(dma.reborrow(), self.info); // Wait for completion asynchronously core::future::poll_fn(|cx| { guard.dma.waker().register(cx.waker()); if guard.dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Transfer completed successfully - clean up without aborting guard.complete(); Ok(len) } /// Blocking write (fallback when DMA is not needed) pub fn blocking_write(&mut self, buf: &[u8]) -> Result<(), Error> { for &byte in buf { while self.info.regs().stat().read().tdre() == Tdre::TXDATA {} self.info.regs().data().write(|w| w.0 = byte as u32); } Ok(()) } /// Flush TX blocking pub fn blocking_flush(&mut self) -> Result<(), Error> { while self.info.regs().water().read().txcount() != 0 {} while self.info.regs().stat().read().tc() == Tc::ACTIVE {} Ok(()) } } impl<'a> LpuartRx<'a, Dma<'a>> { /// Create a new LPUART RX driver with DMA support. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_async_with_dma( _inner: Peri<'a, T>, rx_pin: Peri<'a, impl RxPin>, rx_dma_ch: Peri<'a, impl Channel>, config: Config, ) -> Result { rx_pin.as_rx(); let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); // Initialize LPUART with TX disabled, RX enabled, no flow control let _wg = Lpuart::>::init::(false, true, false, false, config)?; // Enable dma interrupt let dma = DmaChannel::new(rx_dma_ch); dma.enable_interrupt(); Ok(Self::new_inner::( rx_pin, None, Dma { dma, request: T::RX_DMA_REQUEST, }, _wg, )) } /// Read data using DMA. /// /// This configures the DMA channel for a peripheral-to-memory transfer /// and waits for completion asynchronously. Large buffers are automatically /// split into chunks that fit within the DMA transfer limit. /// /// The DMA request source is automatically derived from the LPUART instance type. /// /// # Safety /// /// If the returned future is dropped before completion (e.g., due to a timeout), /// the DMA transfer is automatically aborted to prevent use-after-free. /// /// # Arguments /// * `buf` - Buffer to receive data into pub async fn read(&mut self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); } let mut total = 0; for chunk in buf.chunks_mut(DMA_MAX_TRANSFER_SIZE) { total += self.read_dma_inner(chunk).await?; } Ok(total) } /// Internal helper to read a single chunk (max 0x7FFF bytes) using DMA. async fn read_dma_inner(&mut self, buf: &mut [u8]) -> Result { // First check if there are any RX errors check_and_clear_rx_errors(self.info)?; // We already check in the public function that the length is // 0 < buf.len <= DMA_MAX_TRANSFER_SIZE let len = buf.len(); let peri_addr = self.info.regs().data().as_ptr() as *const u8; let rx_dma = &mut self.mode.dma; unsafe { // Clean up channel state rx_dma.disable_request(); rx_dma.clear_done(); rx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) rx_dma.set_request_source(self.mode.request); // Configure TCD for peripheral-to-memory transfer rx_dma.setup_read_from_peripheral(peri_addr, buf, false, TransferOptions::COMPLETE_INTERRUPT)?; // Enable UART RX DMA request self.info.regs().baud().modify(|w| w.set_rdmae(true)); // Enable DMA channel request rx_dma.enable_request(); } // Create guard that will abort DMA if this future is dropped let guard = RxDmaGuard::new(rx_dma.reborrow(), self.info); // Wait for completion asynchronously core::future::poll_fn(|cx| { guard.dma.waker().register(cx.waker()); if guard.dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Transfer completed successfully - clean up without aborting guard.complete(); Ok(len) } /// Blocking read (fallback when DMA is not needed) pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<(), Error> { for byte in buf.iter_mut() { loop { if has_rx_data_pending(self.info) { *byte = (self.info.regs().data().read().0 & 0xFF) as u8; break; } check_and_clear_rx_errors(self.info)?; } } Ok(()) } pub fn into_ring_dma_rx<'buf: 'a>( &mut self, buf: &'buf mut [u8], ) -> Result, InvalidParameters> { let ring = self.setup_ring_buffer(buf)?; unsafe { ring.enable_dma_request() }; Ok(RingBufferedLpuartRx { ring }) } /// Set up a ring buffer for continuous DMA reception. /// /// This configures the DMA channel for circular operation, enabling continuous /// reception of data without gaps. The DMA will continuously write received /// bytes into the buffer, wrapping around when it reaches the end. /// /// This method encapsulates all the low-level setup: /// - Configures the DMA request source for this LPUART instance /// - Enables the RX DMA request in the LPUART peripheral /// - Sets up the circular DMA transfer /// - Enables the NVIC interrupt for async wakeups /// /// # Arguments /// /// * `buf` - Destination buffer for received data (power-of-2 size is ideal for efficiency) /// /// # Returns /// /// A [`RingBuffer`] that can be used to asynchronously read received data. /// /// # Example /// /// ```rust,no_run /// # #![no_std] /// # #![no_main] /// # /// # extern crate panic_halt; /// # extern crate embassy_mcxa; /// # extern crate embassy_executor; /// # use panic_halt as _; /// # use embassy_executor::Spawner; /// # use embassy_mcxa::clocks::config::Div8; /// # use embassy_mcxa::config::Config; /// # use embassy_mcxa::lpuart; /// # use static_cell::ConstStaticCell; /// # /// static RX_RING_BUFFER: ConstStaticCell<[u8; 64]> = ConstStaticCell::new([0; 64]); /// # #[embassy_executor::main] /// # async fn main(_spawner: Spawner) { /// # let mut cfg = Config::default(); /// # cfg.clock_cfg.sirc.fro_12m_enabled = true; /// # cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); /// # /// # let p = embassy_mcxa::init(cfg); /// # /// # // Create UART configuration /// # let config = lpuart::Config { /// # baudrate_bps: 115_200, /// # ..Default::default() /// # }; /// /// // Create UART instance with DMA channels /// let mut lpuart = lpuart::Lpuart::new_async_with_dma( /// p.LPUART2, // Instance /// p.P2_2, // TX pin /// p.P2_3, // RX pin /// p.DMA_CH0, // TX DMA channel /// p.DMA_CH1, // RX DMA channel /// config, /// ) /// .unwrap(); /// /// let (_, mut rx) = lpuart.split(); /// /// let buf = RX_RING_BUFFER.take(); /// // Set up the ring buffer with circular DMA /// let mut ring_buf = rx.into_ring_dma_rx(buf).unwrap(); /// let mut read_buf = [0u8; 16]; /// let n = ring_buf.read(&mut read_buf).await.unwrap(); /// # } /// ``` fn setup_ring_buffer<'buf: 'a>( &mut self, buf: &'buf mut [u8], ) -> Result, InvalidParameters> { // Get the peripheral data register address let peri_addr = self.info.regs().data().as_ptr() as *const u8; // Configure DMA request source for this LPUART instance (type-safe) unsafe { self.mode.dma.set_request_source(self.mode.request); } // Enable RX DMA request in the LPUART peripheral self.info.regs().baud().modify(|w| w.set_rdmae(true)); // Set up circular DMA transfer (this also enables NVIC interrupt) unsafe { self.mode.dma.setup_circular_read(peri_addr, buf) } } } impl<'peri, 'buf> RingBufferedLpuartRx<'peri, 'buf> { /// Read from the ring buffer pub fn read<'d>( &mut self, dst: &'d mut [u8], ) -> impl Future> + use<'_, 'buf, 'd> { self.ring.read(dst) } /// Clear the current contents of the ring buffer pub fn clear(&mut self) { self.ring.clear(); } } impl<'a> Lpuart<'a, Dma<'a>> { /// Create a new LPUART driver with DMA support for both TX and RX. /// /// Any external pin will be placed into Disabled state upon Drop. pub fn new_async_with_dma( _inner: Peri<'a, T>, tx_pin: Peri<'a, impl TxPin>, rx_pin: Peri<'a, impl RxPin>, tx_dma_ch: Peri<'a, impl Channel>, rx_dma_ch: Peri<'a, impl Channel>, config: Config, ) -> Result { tx_pin.as_tx(); rx_pin.as_rx(); let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); // Initialize LPUART with both TX and RX enabled, no flow control let wg = Lpuart::>::init::(true, true, false, false, config)?; // Enable DMA interrupts let tx_dma = DmaChannel::new(tx_dma_ch); let rx_dma = DmaChannel::new(rx_dma_ch); tx_dma.enable_interrupt(); rx_dma.enable_interrupt(); Ok(Self { tx: LpuartTx::new_inner::( tx_pin, None, Dma { dma: tx_dma, request: T::TX_DMA_REQUEST, }, wg.clone(), ), rx: LpuartRx::new_inner::( rx_pin, None, Dma { dma: rx_dma, request: T::RX_DMA_REQUEST, }, wg, ), }) } /// Write data using DMA pub async fn write(&mut self, buf: &[u8]) -> Result { self.tx.write(buf).await } /// Read data using DMA pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.read(buf).await } } impl<'a> embedded_io_async::Read for LpuartRx<'a, Dma<'a>> { async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { self.read(buf).await } } impl<'a> embedded_io_async::Write for LpuartTx<'a, Dma<'a>> { async fn write(&mut self, buf: &[u8]) -> core::result::Result { self.write(buf).await } async fn flush(&mut self) -> core::result::Result<(), Self::Error> { // No-op, when DMA transfer is completed, it is also flushed. Ok(()) } } impl<'a> embedded_io_async::Read for Lpuart<'a, Dma<'a>> { async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { self.rx.read(buf).await } } impl<'a> embedded_io_async::Write for Lpuart<'a, Dma<'a>> { async fn write(&mut self, buf: &[u8]) -> core::result::Result { self.tx.write(buf).await } async fn flush(&mut self) -> core::result::Result<(), Self::Error> { // No-op, when DMA transfer is completed, it is also flushed. self.tx.flush().await } } ================================================ FILE: embassy-mcxa/src/lpuart/mod.rs ================================================ use core::marker::PhantomData; use core::sync::atomic::{AtomicU8, Ordering}; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitCell; use nxp_pac::lpuart::vals::Dozeen; use paste::paste; use crate::clocks::periph_helpers::{Div4, LpuartClockSel, LpuartConfig}; use crate::clocks::{ClockError, Gate, PoweredClock, WakeGuard, enable_and_reset}; use crate::dma::DmaRequest; use crate::gpio::{AnyPin, SealedPin}; use crate::interrupt::typelevel::Interrupt; use crate::pac::lpuart::vals::{ Idlecfg as IdleConfig, Ilt as IdleType, M as DataBits, Msbf as MsbFirst, Pt as Parity, Rst, Rxflush, Sbns as StopBits, Swap, Tc, Tdre, Txctsc as TxCtsConfig, Txctssrc as TxCtsSource, Txflush, }; use crate::{interrupt, pac}; mod bbq; mod blocking; mod buffered; mod dma; pub use bbq::{ BbqConfig, BbqError, BbqHalfParts, BbqInterruptHandler, BbqParts, BbqRxMode, LpuartBbq, LpuartBbqRx, LpuartBbqTx, }; pub use blocking::Blocking; pub use buffered::{Buffered, BufferedInterruptHandler}; pub use dma::{Dma, RingBufferedLpuartRx}; mod sealed { pub trait Sealed {} } struct State { tx_waker: WaitCell, tx_buf: RingBuffer, rx_waker: WaitCell, rx_buf: RingBuffer, tx_rx_refmask: TxRxRefMask, } /// Value corresponding to either the Tx or the Rx part of the Uart being active. #[derive(Clone, Copy)] #[repr(u8)] enum TxRxRef { Rx = 0b01, Tx = 0b10, } /// Mask that stores whether a Tx and/or Rx part of the Uart is active. /// /// Used in constructors and Drop to manage the peripheral lifetime. struct TxRxRefMask(AtomicU8); impl TxRxRefMask { pub const fn new() -> Self { Self(AtomicU8::new(0)) } /// Atomically signal that either the Tx or Rx has been dropped. /// /// Returns `true` if after this call all parts are inactive. pub fn set_inactive_fetch_last(&self, value: TxRxRef) -> bool { let value = value as u8; self.0.fetch_and(!value, Ordering::AcqRel) & !value == 0 } /// Atomically signal that either the Tx or Rx has been created. pub fn set_active(&self, value: TxRxRef) { let value = value as u8; self.0.fetch_or(value, Ordering::AcqRel); } /// Atomically determine if either channels have been created, but not dropped. Clears the state. /// /// Should only be relevant when any of the parts have been leaked using [core::mem::forget]. pub fn fetch_any_alive_reset(&self) -> bool { self.0.swap(0, Ordering::AcqRel) != 0 } } impl Default for State { fn default() -> Self { Self::new() } } impl State { /// Create a new state instance pub const fn new() -> Self { Self { tx_waker: WaitCell::new(), tx_buf: RingBuffer::new(), rx_waker: WaitCell::new(), rx_buf: RingBuffer::new(), tx_rx_refmask: TxRxRefMask::new(), } } } struct Info { regs: crate::pac::lpuart::Lpuart, int_disable: fn(), mrcc_disable: unsafe fn(), } unsafe impl Sync for Info {} impl Info { #[inline(always)] fn regs(&self) -> crate::pac::lpuart::Lpuart { self.regs } } trait SealedInstance: Gate { fn info() -> &'static Info; fn state() -> &'static State; const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance; const TX_DMA_REQUEST: DmaRequest; const RX_DMA_REQUEST: DmaRequest; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); } /// Trait for LPUART peripheral instances #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_instance { ($($n:expr);* $(;)?) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], int_disable: crate::interrupt::typelevel::[]::disable, mrcc_disable: crate::clocks::disable::]>, }; &INFO } fn state() -> &'static State { static STATE: State = State::new(); &STATE } const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance = crate::clocks::periph_helpers::LpuartInstance::[]; const TX_DMA_REQUEST: DmaRequest = DmaRequest::[]; const RX_DMA_REQUEST: DmaRequest = DmaRequest::[]; const PERF_INT_INCR: fn() = crate::perf_counters::[]; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } // DMA request sources are now type-safe via associated types. // The request source numbers are defined in src/dma.rs: // LPUART0: RX=21, TX=22 -> Lpuart0RxRequest, Lpuart0TxRequest // LPUART1: RX=23, TX=24 -> Lpuart1RxRequest, Lpuart1TxRequest // LPUART2: RX=25, TX=26 -> Lpuart2RxRequest, Lpuart2TxRequest // LPUART3: RX=27, TX=28 -> Lpuart3RxRequest, Lpuart3TxRequest // LPUART4: RX=29, TX=30 -> Lpuart4RxRequest, Lpuart4TxRequest // LPUART5: RX=31, TX=32 -> Lpuart5RxRequest, Lpuart5TxRequest impl_instance!(0; 1; 2; 3; 4); #[cfg(feature = "mcxa5xx")] impl_instance!(5); /// Perform software reset on the LPUART peripheral fn perform_software_reset(info: &'static Info) { // Software reset - set and clear RST bit (Global register) info.regs().global().write(|w| w.set_rst(Rst::RESET)); info.regs().global().write(|w| w.set_rst(Rst::NO_EFFECT)); } /// Disable both transmitter and receiver fn disable_transceiver(info: &'static Info) { info.regs().ctrl().modify(|w| { w.set_te(false); w.set_re(false); }); } /// Calculate and configure baudrate settings fn configure_baudrate(info: &'static Info, baudrate_bps: u32, clock_freq: u32) -> Result<(), Error> { let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; // Configure BAUD register info.regs().baud().modify(|w| { // Clear and set OSR w.set_osr(osr - 1); // Clear and set SBR w.set_sbr(sbr); // Set BOTHEDGE if OSR is between 4 and 7 w.set_bothedge(osr > 3 && osr < 8); }); Ok(()) } /// Configure frame format (stop bits, data bits) fn configure_frame_format(info: &'static Info, config: &Config) { // Configure stop bits info.regs().baud().modify(|w| w.set_sbns(config.stop_bits_count)); // Clear M10 for now (10-bit mode) info.regs().baud().modify(|w| w.set_m10(false)); } /// Configure control settings (parity, data bits, idle config, pin swap) fn configure_control_settings(info: &'static Info, config: &Config) { info.regs().ctrl().modify(|w| { // Parity configuration if let Some(parity) = config.parity_mode { w.set_pe(true); w.set_pt(parity); } else { w.set_pe(false); }; // Allow the lpuart to wake from deep sleep if configured to // work in deep sleep mode. // // NOTE: this is the default state, and setting this to `Dozeen::DISABLED` // seems to actively *stop* the uart, regardless of whether automatic clock // gating is used, or if the device never goes to deep sleep at all (e.g. // in WfeUngated configuration). For now, let's not touch this unless we // actually need to, e.g. *forcing* the lpuart to sleep! w.set_dozeen(Dozeen::ENABLED); // Data bits configuration match config.data_bits_count { DataBits::DATA8 => { if config.parity_mode.is_some() { w.set_m(DataBits::DATA9); // 8 data + 1 parity = 9 bits } else { w.set_m(DataBits::DATA8); // 8 data bits only } } DataBits::DATA9 => w.set_m(DataBits::DATA9), }; // Idle configuration w.set_idlecfg(config.rx_idle_config); w.set_ilt(config.rx_idle_type); // Swap TXD/RXD if configured if config.swap_txd_rxd { w.set_swap(Swap::SWAP); } else { w.set_swap(Swap::STANDARD); } }); } /// Configure FIFO settings and watermarks fn configure_fifo(info: &'static Info, config: &Config) { // Configure WATER register for FIFO watermarks info.regs().water().write(|w| { w.set_rxwater(config.rx_fifo_watermark); w.set_txwater(config.tx_fifo_watermark); }); // Enable TX/RX FIFOs info.regs().fifo().modify(|w| { w.set_txfe(true); w.set_rxfe(true); }); // Flush FIFOs info.regs().fifo().modify(|w| { w.set_txflush(Txflush::TXFIFO_RST); w.set_rxflush(Rxflush::RXFIFO_RST); }); } /// Clear all status flags fn clear_all_status_flags(info: &'static Info) { info.regs().stat().modify(|_w| { // Write back all values, clearing the W1C fields implicitly. }); } /// Configure hardware flow control if enabled fn configure_flow_control(info: &'static Info, enable_tx_cts: bool, enable_rx_rts: bool, config: &Config) { if enable_rx_rts || enable_tx_cts { info.regs().modir().modify(|w| { w.set_txctsc(config.tx_cts_config); w.set_txctssrc(config.tx_cts_source); w.set_rxrtse(enable_rx_rts); w.set_txctse(enable_tx_cts); }); } } /// Configure bit order (MSB first or LSB first) fn configure_bit_order(info: &'static Info, msb_first: MsbFirst) { info.regs().stat().modify(|w| w.set_msbf(msb_first)); } /// Enable transmitter and/or receiver based on configuration fn enable_transceiver(info: &'static Info, enable_tx: bool, enable_rx: bool) { info.regs().ctrl().modify(|w| { if enable_tx { w.set_te(true); } if enable_rx { w.set_re(true); } }); } fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16), Error> { let mut baud_diff = baudrate; let mut osr = 0u8; let mut sbr = 0u16; // Try OSR values from 4 to 32 for osr_temp in 4u8..=32u8 { // Calculate SBR: (srcClock_Hz * 2 / (baudRate * osr) + 1) / 2 let sbr_calc = ((src_clock_hz * 2) / (baudrate * osr_temp as u32)).div_ceil(2); let sbr_temp = if sbr_calc == 0 { 1 } else if sbr_calc > 0x1FFF { 0x1FFF } else { sbr_calc as u16 }; // Calculate actual baud rate let calculated_baud = src_clock_hz / (osr_temp as u32 * sbr_temp as u32); let temp_diff = calculated_baud.abs_diff(baudrate); if temp_diff <= baud_diff { baud_diff = temp_diff; osr = osr_temp; sbr = sbr_temp; } } // Check if baud rate difference is within 3% if baud_diff > (baudrate / 100) * 3 { return Err(Error::UnsupportedBaudrate); } Ok((osr, sbr)) } /// Wait for all transmit operations to complete fn wait_for_tx_complete(info: &'static Info) { // Wait for TX FIFO to empty while info.regs().water().read().txcount() != 0 { // Wait for TX FIFO to drain } // Wait for last character to shift out (TC = Transmission Complete) while info.regs().stat().read().tc() == Tc::ACTIVE { // Wait for transmission to complete } } fn check_and_clear_rx_errors(info: &'static Info) -> Result<(), Error> { let stat = info.regs().stat().read(); // Check for overrun first - other error flags are prevented when OR is set let or_set = stat.or(); let pf_set = stat.pf(); let fe_set = stat.fe(); let nf_set = stat.nf(); // Clear all errors before returning info.regs().stat().write(|w| { w.set_or(or_set); w.set_pf(pf_set); w.set_fe(fe_set); w.set_nf(nf_set); }); // Return error source if or_set { Err(Error::Overrun) } else if pf_set { Err(Error::Parity) } else if fe_set { Err(Error::Framing) } else if nf_set { Err(Error::Noise) } else { Ok(()) } } fn has_rx_data_pending(info: &'static Info) -> bool { if info.regs().param().read().rxfifo() > 0 { // FIFO is available - check RXCOUNT in WATER register info.regs().water().read().rxcount() > 0 } else { // No FIFO - check RDRF flag in STAT register info.regs().stat().read().rdrf() } } impl sealed::Sealed for T {} pub trait TxPin: Into + sealed::Sealed + PeripheralType { const MUX: crate::pac::port::vals::Mux; /// convert the pin to appropriate function for Lpuart Tx usage fn as_tx(&self); } pub trait RxPin: Into + sealed::Sealed + PeripheralType { const MUX: crate::pac::port::vals::Mux; /// convert the pin to appropriate function for Lpuart Rx usage fn as_rx(&self); } pub trait CtsPin: Into + sealed::Sealed + PeripheralType { const MUX: crate::pac::port::vals::Mux; /// convert the pin to appropriate function for Lpuart Cts usage fn as_cts(&self); } pub trait RtsPin: Into + sealed::Sealed + PeripheralType { const MUX: crate::pac::port::vals::Mux; /// convert the pin to appropriate function for Lpuart Rts usage fn as_rts(&self); } macro_rules! impl_tx_pin { ($inst:ident, $pin:ident, $alt:ident) => { impl TxPin for crate::peripherals::$pin { const MUX: crate::pac::port::vals::Mux = crate::pac::port::vals::Mux::$alt; fn as_tx(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Normal.into()); self.set_function(>::MUX); self.set_enable_input_buffer(false); } } }; } macro_rules! impl_rx_pin { ($inst:ident, $pin:ident, $alt:ident) => { impl RxPin for crate::peripherals::$pin { const MUX: crate::pac::port::vals::Mux = crate::pac::port::vals::Mux::$alt; fn as_rx(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_function(>::MUX); self.set_enable_input_buffer(true); } } }; } macro_rules! impl_cts_pin { ($inst:ident, $pin:ident, $alt:ident) => { impl CtsPin for crate::peripherals::$pin { const MUX: crate::pac::port::vals::Mux = crate::pac::port::vals::Mux::$alt; fn as_cts(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_function(>::MUX); self.set_enable_input_buffer(true); } } }; } macro_rules! impl_rts_pin { ($inst:ident, $pin:ident, $alt:ident) => { impl RtsPin for crate::peripherals::$pin { const MUX: crate::pac::port::vals::Mux = crate::pac::port::vals::Mux::$alt; fn as_rts(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Normal.into()); self.set_function(>::MUX); self.set_enable_input_buffer(false); } } }; } // LPUART 0 #[cfg(feature = "jtag-extras-as-gpio")] impl_tx_pin!(LPUART0, P0_3, MUX2); impl_tx_pin!(LPUART0, P0_21, MUX3); impl_tx_pin!(LPUART0, P2_1, MUX2); #[cfg(feature = "mcxa5xx")] impl_tx_pin!(LPUART0, P4_9, MUX2); #[cfg(feature = "swd-swo-as-gpio")] impl_rx_pin!(LPUART0, P0_2, MUX2); impl_rx_pin!(LPUART0, P0_20, MUX3); impl_rx_pin!(LPUART0, P2_0, MUX2); #[cfg(feature = "mcxa5xx")] impl_rx_pin!(LPUART0, P4_8, MUX2); #[cfg(feature = "swd-as-gpio")] impl_cts_pin!(LPUART0, P0_1, MUX2); impl_cts_pin!(LPUART0, P0_23, MUX3); impl_cts_pin!(LPUART0, P2_3, MUX2); #[cfg(feature = "mcxa5xx")] impl_cts_pin!(LPUART0, P4_11, MUX2); #[cfg(feature = "swd-as-gpio")] impl_rts_pin!(LPUART0, P0_0, MUX2); impl_rts_pin!(LPUART0, P0_22, MUX3); impl_rts_pin!(LPUART0, P2_2, MUX2); #[cfg(feature = "mcxa5xx")] impl_rts_pin!(LPUART0, P4_10, MUX2); // LPUART 1 impl_tx_pin!(LPUART1, P1_9, MUX2); impl_tx_pin!(LPUART1, P2_13, MUX3); impl_tx_pin!(LPUART1, P3_9, MUX3); impl_tx_pin!(LPUART1, P3_21, MUX3); impl_rx_pin!(LPUART1, P1_8, MUX2); impl_rx_pin!(LPUART1, P2_12, MUX3); impl_rx_pin!(LPUART1, P3_8, MUX3); impl_rx_pin!(LPUART1, P3_20, MUX3); impl_cts_pin!(LPUART1, P1_11, MUX2); impl_cts_pin!(LPUART1, P2_17, MUX3); impl_cts_pin!(LPUART1, P3_11, MUX3); impl_cts_pin!(LPUART1, P3_23, MUX3); impl_rts_pin!(LPUART1, P1_10, MUX2); impl_rts_pin!(LPUART1, P2_15, MUX3); impl_rts_pin!(LPUART1, P2_16, MUX3); impl_rts_pin!(LPUART1, P3_10, MUX3); #[cfg(feature = "mcxa5xx")] impl_rts_pin!(LPUART1, P3_22, MUX3); // LPUART 2 impl_tx_pin!(LPUART2, P1_5, MUX3); impl_tx_pin!(LPUART2, P1_13, MUX3); impl_tx_pin!(LPUART2, P2_2, MUX3); impl_tx_pin!(LPUART2, P2_10, MUX3); impl_tx_pin!(LPUART2, P3_15, MUX2); impl_rx_pin!(LPUART2, P1_4, MUX3); impl_rx_pin!(LPUART2, P1_12, MUX3); impl_rx_pin!(LPUART2, P2_3, MUX3); impl_rx_pin!(LPUART2, P2_11, MUX3); impl_rx_pin!(LPUART2, P3_14, MUX2); impl_cts_pin!(LPUART2, P1_7, MUX3); impl_cts_pin!(LPUART2, P1_15, MUX3); impl_cts_pin!(LPUART2, P2_4, MUX3); impl_cts_pin!(LPUART2, P3_13, MUX2); impl_rts_pin!(LPUART2, P1_6, MUX3); impl_rts_pin!(LPUART2, P1_14, MUX3); impl_rts_pin!(LPUART2, P2_5, MUX3); impl_rts_pin!(LPUART2, P3_12, MUX2); // LPUART 3 impl_tx_pin!(LPUART3, P3_1, MUX3); impl_tx_pin!(LPUART3, P3_12, MUX3); impl_tx_pin!(LPUART3, P4_5, MUX3); impl_rx_pin!(LPUART3, P3_0, MUX3); impl_rx_pin!(LPUART3, P3_13, MUX3); impl_rx_pin!(LPUART3, P4_2, MUX3); impl_cts_pin!(LPUART3, P3_7, MUX3); impl_cts_pin!(LPUART3, P3_14, MUX3); impl_cts_pin!(LPUART3, P4_6, MUX3); impl_rts_pin!(LPUART3, P3_6, MUX3); impl_rts_pin!(LPUART3, P3_15, MUX3); impl_rts_pin!(LPUART3, P4_7, MUX3); // LPUART 4 impl_tx_pin!(LPUART4, P2_7, MUX3); impl_tx_pin!(LPUART4, P3_19, MUX2); impl_tx_pin!(LPUART4, P3_27, MUX3); impl_tx_pin!(LPUART4, P4_3, MUX3); impl_rx_pin!(LPUART4, P2_6, MUX3); impl_rx_pin!(LPUART4, P3_18, MUX2); #[cfg(feature = "mcxa2xx")] impl_rx_pin!(LPUART4, P3_28, MUX3); impl_rx_pin!(LPUART4, P4_4, MUX3); impl_cts_pin!(LPUART4, P2_0, MUX3); impl_cts_pin!(LPUART4, P3_17, MUX2); #[cfg(feature = "mcxa2xx")] impl_cts_pin!(LPUART4, P3_31, MUX3); impl_rts_pin!(LPUART4, P2_1, MUX3); impl_rts_pin!(LPUART4, P3_16, MUX2); #[cfg(feature = "mcxa2xx")] impl_rts_pin!(LPUART4, P3_30, MUX3); // LPUART 5 #[cfg(feature = "mcxa5xx")] impl_tx_pin!(LPUART5, P0_25, MUX8); #[cfg(feature = "mcxa5xx")] impl_tx_pin!(LPUART5, P1_10, MUX8); #[cfg(feature = "mcxa5xx")] impl_tx_pin!(LPUART5, P1_17, MUX8); #[cfg(feature = "mcxa5xx")] impl_tx_pin!(LPUART5, P3_10, MUX8); #[cfg(feature = "mcxa5xx")] impl_rx_pin!(LPUART5, P0_24, MUX8); #[cfg(feature = "mcxa5xx")] impl_rx_pin!(LPUART5, P1_11, MUX8); #[cfg(feature = "mcxa5xx")] impl_rx_pin!(LPUART5, P1_16, MUX8); #[cfg(feature = "mcxa5xx")] impl_rx_pin!(LPUART5, P3_11, MUX8); #[cfg(feature = "mcxa5xx")] impl_cts_pin!(LPUART5, P0_27, MUX8); #[cfg(feature = "mcxa5xx")] impl_cts_pin!(LPUART5, P1_12, MUX8); #[cfg(feature = "mcxa5xx")] impl_cts_pin!(LPUART5, P1_19, MUX8); #[cfg(feature = "mcxa5xx")] impl_cts_pin!(LPUART5, P3_8, MUX8); #[cfg(feature = "mcxa5xx")] impl_rts_pin!(LPUART5, P0_26, MUX8); #[cfg(feature = "mcxa5xx")] impl_rts_pin!(LPUART5, P1_13, MUX8); #[cfg(feature = "mcxa5xx")] impl_rts_pin!(LPUART5, P1_18, MUX8); #[cfg(feature = "mcxa5xx")] impl_rts_pin!(LPUART5, P3_9, MUX8); /// LPUART error types #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// Read error Read, /// Buffer overflow Overrun, /// Noise error Noise, /// Framing error Framing, /// Parity error Parity, /// Failure Fail, /// Invalid argument InvalidArgument, /// Lpuart baud rate cannot be supported with the given clock UnsupportedBaudrate, /// RX FIFO Empty RxFifoEmpty, /// TX FIFO Full TxFifoFull, /// TX Busy TxBusy, /// Clock Error ClockSetup(ClockError), /// Other internal errors or unexpected state. Other, } impl From for Error { fn from(_value: crate::dma::InvalidParameters) -> Self { Error::Other } } impl From for Error { fn from(_value: maitake_sync::Closed) -> Self { Error::Other } } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Error::Read => write!(f, "Read error"), Error::Overrun => write!(f, "Buffer overflow"), Error::Noise => write!(f, "Noise error"), Error::Framing => write!(f, "Framing error"), Error::Parity => write!(f, "Parity error"), Error::Fail => write!(f, "Failure"), Error::InvalidArgument => write!(f, "Invalid argument"), Error::UnsupportedBaudrate => write!(f, "Unsupported baud rate"), Error::RxFifoEmpty => write!(f, "RX FIFO empty"), Error::TxFifoFull => write!(f, "TX FIFO full"), Error::TxBusy => write!(f, "TX busy"), Error::ClockSetup(e) => write!(f, "Clock setup error: {:?}", e), Error::Other => write!(f, "Other error"), } } } impl core::error::Error for Error {} /// Lpuart config #[derive(Debug, Clone, Copy)] pub struct Config { /// Power state required for this peripheral pub power: PoweredClock, /// Clock source pub source: LpuartClockSel, /// Clock divisor pub div: Div4, /// Baud rate in bits per second pub baudrate_bps: u32, /// Parity configuration pub parity_mode: Option, /// Number of data bits pub data_bits_count: DataBits, /// MSB First or LSB First configuration pub msb_first: MsbFirst, /// Number of stop bits pub stop_bits_count: StopBits, /// TX FIFO watermark pub tx_fifo_watermark: u8, /// RX FIFO watermark pub rx_fifo_watermark: u8, /// TX CTS source pub tx_cts_source: TxCtsSource, /// TX CTS configure pub tx_cts_config: TxCtsConfig, /// RX IDLE type pub rx_idle_type: IdleType, /// RX IDLE configuration pub rx_idle_config: IdleConfig, /// Swap TXD and RXD pins pub swap_txd_rxd: bool, } impl Default for Config { fn default() -> Self { Self { baudrate_bps: 115_200u32, parity_mode: None, data_bits_count: DataBits::DATA8, msb_first: MsbFirst::LSB_FIRST, stop_bits_count: StopBits::ONE, tx_fifo_watermark: 0, rx_fifo_watermark: 1, tx_cts_source: TxCtsSource::CTS, tx_cts_config: TxCtsConfig::START, rx_idle_type: IdleType::FROM_START, rx_idle_config: IdleConfig::IDLE_1, swap_txd_rxd: false, power: PoweredClock::NormalEnabledDeepSleepDisabled, source: LpuartClockSel::FroLfDiv, div: Div4::no_div(), } } } /// Driver mode. #[allow(private_bounds)] pub trait Mode: sealed::Sealed {} /// Lpuart driver. pub struct Lpuart<'a, M: Mode> { tx: LpuartTx<'a, M>, rx: LpuartRx<'a, M>, } struct TxPins<'a> { tx_pin: Peri<'a, AnyPin>, cts_pin: Option>, } impl<'a> TxPins<'a> { fn take(self) -> (Peri<'a, AnyPin>, Option>) { unsafe { let tx_pin = self.tx_pin.clone_unchecked(); let cts_pin = self.cts_pin.as_ref().map(|p| p.clone_unchecked()); core::mem::forget(self); (tx_pin, cts_pin) } } } struct RxPins<'a> { rx_pin: Peri<'a, AnyPin>, rts_pin: Option>, } impl<'a> RxPins<'a> { fn take(self) -> (Peri<'a, AnyPin>, Option>) { unsafe { let rx_pin = self.rx_pin.clone_unchecked(); let rts_pin = self.rts_pin.as_ref().map(|p| p.clone_unchecked()); core::mem::forget(self); (rx_pin, rts_pin) } } } impl Drop for TxPins<'_> { fn drop(&mut self) { self.tx_pin.set_as_disabled(); if let Some(cts_pin) = &self.cts_pin { cts_pin.set_as_disabled(); } } } impl Drop for RxPins<'_> { fn drop(&mut self) { self.rx_pin.set_as_disabled(); if let Some(rts_pin) = &self.rts_pin { rts_pin.set_as_disabled(); } } } /// Lpuart TX driver. pub struct LpuartTx<'a, M: Mode> { info: &'static Info, state: &'static State, mode: M, _tx_pins: TxPins<'a>, _wg: Option, _phantom: PhantomData<&'a ()>, } /// Lpuart Rx driver. pub struct LpuartRx<'a, M: Mode> { info: &'static Info, state: &'static State, mode: M, _rx_pins: RxPins<'a>, _wg: Option, _phantom: PhantomData<&'a ()>, } fn disable_peripheral(info: &'static Info) { // Clear all status flags clear_all_status_flags(info); // Disable interrupts at the NVIC level (info.int_disable)(); // Disable the module - clear all CTRL register bits info.regs().ctrl().write(|w| w.0 = 0); // Disable at the MRCC level unsafe { (info.mrcc_disable)(); } } impl Drop for LpuartTx<'_, M> { fn drop(&mut self) { // Wait for TX operations to complete. We cannot load more items // into the fifo as we have exclusive access to the LpuartTx wait_for_tx_complete(self.info); // Disable transmit interrupts to prevent usage of the buffer space cortex_m::interrupt::free(|_| { self.info.regs().ctrl().modify(|w| w.set_tie(false)); }); // De-init the tx buffer, as once '_ ends we no longer can guarantee // our usage of the buffer is sound. unsafe { self.state.tx_buf.deinit(); } if self.state.tx_rx_refmask.set_inactive_fetch_last(TxRxRef::Tx) { disable_peripheral(self.info); } } } impl Drop for LpuartRx<'_, M> { fn drop(&mut self) { // Disable receive interrupts to prevent future usage of the buffer space cortex_m::interrupt::free(|_| { self.info.regs().ctrl().modify(|w| { w.set_rie(false); // RX interrupt w.set_orie(false); // Overrun interrupt w.set_peie(false); // Parity error interrupt w.set_feie(false); // Framing error interrupt w.set_neie(false); // Noise error interrupt }); }); // De-init the rx buffer, as once '_ ends we no longer can guarantee // our usage of the buffer is sound. unsafe { self.state.rx_buf.deinit(); } if self.state.tx_rx_refmask.set_inactive_fetch_last(TxRxRef::Rx) { disable_peripheral(self.info); } } } impl<'a, M: Mode> Lpuart<'a, M> { fn init( enable_tx: bool, enable_rx: bool, enable_tx_cts: bool, enable_rx_rts: bool, config: Config, ) -> Result, Error> { // Check if the peripheral was leaked using [core::mem::forget], and clean up the peripheral. if T::state().tx_rx_refmask.fetch_any_alive_reset() { disable_peripheral(T::info()); } // Enable clocks let conf = LpuartConfig { power: config.power, source: config.source, div: config.div, instance: T::CLOCK_INSTANCE, }; let parts = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; // Perform initialization sequence perform_software_reset(T::info()); disable_transceiver(T::info()); configure_baudrate(T::info(), config.baudrate_bps, parts.freq)?; configure_frame_format(T::info(), &config); configure_control_settings(T::info(), &config); configure_fifo(T::info(), &config); clear_all_status_flags(T::info()); configure_flow_control(T::info(), enable_tx_cts, enable_rx_rts, &config); configure_bit_order(T::info(), config.msb_first); enable_transceiver(T::info(), enable_tx, enable_rx); Ok(parts.wake_guard) } /// Split the Lpuart into a transmitter and receiver pub fn split(self) -> (LpuartTx<'a, M>, LpuartRx<'a, M>) { (self.tx, self.rx) } /// Split the Lpuart into a transmitter and receiver by mutable reference pub fn split_ref(&mut self) -> (&mut LpuartTx<'a, M>, &mut LpuartRx<'a, M>) { (&mut self.tx, &mut self.rx) } } impl<'a, M: Mode> LpuartTx<'a, M> { fn new_inner( tx_pin: Peri<'a, AnyPin>, cts_pin: Option>, mode: M, wg: Option, ) -> Self { T::state().tx_rx_refmask.set_active(TxRxRef::Tx); Self { info: T::info(), state: T::state(), mode, _tx_pins: TxPins { tx_pin, cts_pin }, _wg: wg, _phantom: PhantomData, } } } impl<'a, M: Mode> LpuartRx<'a, M> { fn new_inner( rx_pin: Peri<'a, AnyPin>, rts_pin: Option>, mode: M, _wg: Option, ) -> Self { T::state().tx_rx_refmask.set_active(TxRxRef::Rx); Self { info: T::info(), state: T::state(), mode, _rx_pins: RxPins { rx_pin, rts_pin }, _wg, _phantom: PhantomData, } } } 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::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, _ => embedded_hal_nb::serial::ErrorKind::Other, } } } impl embedded_hal_nb::serial::ErrorType for LpuartRx<'_, M> { type Error = Error; } impl embedded_hal_nb::serial::ErrorType for LpuartTx<'_, M> { type Error = Error; } impl embedded_hal_nb::serial::ErrorType for Lpuart<'_, M> { type Error = Error; } impl embedded_io::Error for Error { fn kind(&self) -> embedded_io::ErrorKind { embedded_io::ErrorKind::Other } } impl embedded_io::ErrorType for LpuartRx<'_, M> { type Error = Error; } impl embedded_io::ErrorType for LpuartTx<'_, M> { type Error = Error; } impl embedded_io::ErrorType for Lpuart<'_, M> { type Error = Error; } ================================================ FILE: embassy-mcxa/src/ostimer.rs ================================================ //! Time Driver. use core::cell::{Cell, RefCell}; use core::sync::atomic::{AtomicBool, Ordering}; use critical_section::CriticalSection; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_time_driver::Driver; use embassy_time_queue_utils::Queue; use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; use crate::clocks::{PoweredClock, enable_and_reset}; use crate::interrupt; use crate::interrupt::InterruptExt; use crate::pac::OSTIMER0; use crate::peripherals::OSTIMER0; struct AlarmState { timestamp: Cell, } unsafe impl Send for AlarmState {} impl AlarmState { const fn new() -> Self { Self { timestamp: Cell::new(u64::MAX), } } } /// Convert gray to decimal /// /// Os Event provides a 64-bit timestamp gray-encoded. All we have to /// do here is read both 32-bit halves of the register and convert /// from gray to regular binary. fn gray_to_dec(gray: u64) -> u64 { let mut dec = gray; dec ^= dec >> 1; dec ^= dec >> 2; dec ^= dec >> 4; dec ^= dec >> 8; dec ^= dec >> 16; dec ^= dec >> 32; dec } /// Convert decimal to gray /// /// Before writing match value to the target register, we must convert /// it back into gray code. fn dec_to_gray(dec: u64) -> u64 { let gray = dec; gray ^ (gray >> 1) } embassy_time_driver::time_driver_impl!(static DRIVER: OsTimer = OsTimer { alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), queue: Mutex::new(RefCell::new(Queue::new())), }); struct OsTimer { /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. alarms: Mutex, queue: Mutex>, } impl OsTimer { fn init(&'static self, irq_prio: crate::interrupt::Priority) { // init alarms critical_section::with(|cs| { let alarm = DRIVER.alarms.borrow(cs); alarm.timestamp.set(u64::MAX); }); let parts = unsafe { enable_and_reset::(&OsTimerConfig { power: PoweredClock::AlwaysEnabled, source: OstimerClockSel::Clk1M, }) .expect("Enabling OsTimer clock should not fail") }; // Currently does nothing as Clk1M is always enabled anyway, this is here // to make sure that doesn't change in a refactoring. core::mem::forget(parts.wake_guard); interrupt::OS_EVENT.disable(); // Make sure interrupt is masked OSTIMER0.osevent_ctrl().modify(|w| w.set_ostimer_intena(false)); // Default to the end of time OSTIMER0.match_l().write(|w| w.set_match_value(u32::MAX)); OSTIMER0.match_h().write(|w| w.set_match_value(u16::MAX)); interrupt::OS_EVENT.unpend(); interrupt::OS_EVENT.set_priority(irq_prio); unsafe { interrupt::OS_EVENT.enable() }; } /// Set an alarm for timestamp, returning false if the time has already expired. fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { let alarm = self.alarms.borrow(cs); alarm.timestamp.set(timestamp); // Wait until we're allowed to write to MATCH_L/MATCH_H registers while OSTIMER0.osevent_ctrl().read().match_wr_rdy() {} let gray_timestamp = dec_to_gray(timestamp); OSTIMER0.match_l().write(|w| w.set_match_value(gray_timestamp as u32)); OSTIMER0 .match_h() .write(|w| w.set_match_value((gray_timestamp >> 32) as u16)); // Check if the timestamp has already expired, which could mean the just set match value would never match. let t = self.now(); if timestamp <= t { OSTIMER0.osevent_ctrl().modify(|w| w.set_ostimer_intena(false)); alarm.timestamp.set(u64::MAX); return false; } // Enable interrupt. If the timestamp already matched, this would immediately pend the interrupt. OSTIMER0.osevent_ctrl().modify(|w| w.set_ostimer_intena(true)); 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()); } } fn on_interrupt(&self) { crate::perf_counters::incr_interrupt_ostimer(); critical_section::with(|cs| { if OSTIMER0.osevent_ctrl().read().ostimer_intrflag() { OSTIMER0.osevent_ctrl().modify(|w| { w.set_ostimer_intena(false); w.set_ostimer_intrflag(true) }); crate::perf_counters::incr_interrupt_ostimer_alarm(); self.trigger_alarm(cs); } }); } } static INIT: AtomicBool = AtomicBool::new(false); impl Driver for OsTimer { fn now(&self) -> u64 { // Don't try to read the timer before the OsTimer is actually enabled. // This leads to faults on the MCX-A. if !INIT.load(Ordering::Relaxed) { return 0; } let mut t = OSTIMER0.evtimerh().read().0 as u64; t <<= 32; t |= OSTIMER0.evtimerl().read().evtimer_count_value() as u64; gray_to_dec(t) } fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { critical_section::with(|cs| { let mut queue = self.queue.borrow(cs).borrow_mut(); if queue.schedule_wake(at, waker) { let mut next = queue.next_expiration(self.now()); while !self.set_alarm(cs, next) { next = queue.next_expiration(self.now()); } } }) } } #[allow(non_snake_case)] #[interrupt] fn OS_EVENT() { DRIVER.on_interrupt() } pub(crate) fn init(irq_prio: crate::interrupt::Priority) { DRIVER.init(irq_prio); INIT.store(true, Ordering::Relaxed); } ================================================ FILE: embassy-mcxa/src/perf_counters.rs ================================================ //! # Performance counters //! //! This module contains simple performance counters, intended to aid debugging //! for metrics like "number of interrupts served" per peripheral, etc. //! //! When the `perf` feature is active, then the performance counters are functional. //! When the `perf` feature is NOT active, the "increment" and "clear" interfaces are //! still available, but act as a no-op. #[cfg_attr(not(feature = "perf"), allow(unused_imports))] use core::sync::atomic::{AtomicU32, Ordering}; use paste::paste; macro_rules! define_counters { ($( $(#[$attr:meta])* $name:ident),*) => { #[cfg_attr(not(feature = "perf"), allow(dead_code))] static PERF_COUNTERS: Counters = Counters::new(); impl Counters { const fn new() -> Self { Self { $( $(#[$attr])* #[cfg(feature = "perf")] $name: AtomicU32::new(0), )* } } } paste! { /// Reset all perf counters to zero #[cfg(feature = "perf")] pub fn clear_all() { $( [](); )* } /// Get a snapshot report of all perf counters #[cfg(feature = "perf")] pub fn get_report() -> Report { Report { $( $name: ([])(), )* } } /// Get a snapshot report of all perf counters, and also reset all counters to zero #[cfg(feature = "perf")] pub fn get_report_and_clear() -> Report { Report { $( $name: ([])(), )* } } $( /// Increment perf counter by 1 $(#[$attr])* #[inline(always)] pub fn []() { #[cfg(feature = "perf")] PERF_COUNTERS.$name.fetch_add(1, Ordering::Relaxed); } /// Reset perf counter to zero $(#[$attr])* #[inline(always)] pub fn []() { #[cfg(feature = "perf")] PERF_COUNTERS.$name.store(0, Ordering::Relaxed); } /// Get current perf counter snapshot /// /// If the `perf` feature is not enabled, this always returns zero $(#[$attr])* #[inline(always)] pub fn []() -> u32 { #[cfg(feature = "perf")] let ret = PERF_COUNTERS.$name.load(Ordering::Relaxed); #[cfg(not(feature = "perf"))] let ret = 0; ret } /// Get current perf counter snapshot and reset the perf counter to zero /// /// If the `perf` feature is not enabled, this always returns zero $(#[$attr])* #[inline(always)] pub fn []() -> u32 { #[cfg(feature = "perf")] let ret = PERF_COUNTERS.$name.swap(0, Ordering::Relaxed); #[cfg(not(feature = "perf"))] let ret = 0; ret } )* } /// A snapshot report of all perf counters #[cfg(feature = "perf")] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Report { $( $(#[$attr])* pub $name: u32, )* } struct Counters { $( $(#[$attr])* #[cfg(feature = "perf")] $name: AtomicU32, )* } }; } // TODO: In the future, we may want to have more granular groupings of counters, behind // features like `perf-interrupt`, `perf-interrupt-wake`, `perf-sleep`, etc. In that case, // we might want a macro like the following, that enables a perf counter for ANY of the // given features: // // ```rust // define_counters! { // (["perf-interrupt", "perf-adc"], interrupt_adc0), // }; // // We can implement this later if we decide that "all of the perf counters" takes up too // much static RAM space. define_counters!( deep_sleeps, interrupt_adc0, interrupt_adc1, interrupt_adc2, interrupt_adc3, interrupt_cdog0, interrupt_ctimer0, interrupt_ctimer0_wake, interrupt_ctimer1, interrupt_ctimer1_wake, interrupt_ctimer2, interrupt_ctimer2_wake, interrupt_ctimer3, interrupt_ctimer3_wake, interrupt_ctimer4, interrupt_ctimer4_wake, interrupt_edma0, interrupt_edma0_wake, interrupt_gpio0, interrupt_gpio0_wake, interrupt_gpio1, interrupt_gpio1_wake, interrupt_gpio2, interrupt_gpio2_wake, interrupt_gpio3, interrupt_gpio3_wake, interrupt_gpio4, interrupt_gpio4_wake, interrupt_gpio5, interrupt_gpio5_wake, interrupt_i2c0, interrupt_i2c0_wake, interrupt_i2c1, interrupt_i2c1_wake, interrupt_i2c2, interrupt_i2c2_wake, interrupt_i2c3, interrupt_i2c3_wake, interrupt_i3c0, interrupt_i3c0_wake, #[cfg(feature = "mcxa5xx")] interrupt_i3c1, #[cfg(feature = "mcxa5xx")] interrupt_i3c1_wake, #[cfg(feature = "mcxa5xx")] interrupt_i3c2, #[cfg(feature = "mcxa5xx")] interrupt_i3c2_wake, #[cfg(feature = "mcxa5xx")] interrupt_i3c3, #[cfg(feature = "mcxa5xx")] interrupt_i3c3_wake, interrupt_lpuart0, interrupt_lpuart0_wake, interrupt_lpuart1, interrupt_lpuart1_wake, interrupt_lpuart2, interrupt_lpuart2_wake, interrupt_lpuart3, interrupt_lpuart3_wake, interrupt_lpuart4, interrupt_lpuart4_wake, interrupt_lpuart5, interrupt_lpuart5_wake, interrupt_ostimer, interrupt_ostimer_alarm, interrupt_rtc0, interrupt_rtc0_wake, interrupt_spi0, interrupt_spi0_wake, interrupt_spi1, interrupt_spi1_wake, #[cfg(feature = "mcxa5xx")] interrupt_spi2, #[cfg(feature = "mcxa5xx")] interrupt_spi2_wake, #[cfg(feature = "mcxa5xx")] interrupt_spi3, #[cfg(feature = "mcxa5xx")] interrupt_spi3_wake, #[cfg(feature = "mcxa5xx")] interrupt_spi4, #[cfg(feature = "mcxa5xx")] interrupt_spi4_wake, #[cfg(feature = "mcxa5xx")] interrupt_spi5, #[cfg(feature = "mcxa5xx")] interrupt_spi5_wake, interrupt_trng0, interrupt_trng0_wake, interrupt_wwdt, wfe_sleeps ); ================================================ FILE: embassy-mcxa/src/reset_reason.rs ================================================ //! Reset reason //! //! MCXA families keep the most recent reset reason in the SRS //! register of the CMC block. This lets users understand why the MCU //! has reset and take appropriate corrective actions if required. //! //! The reset reason bits are cached for the during of this boot, //! allowing the user to query the reset reason as many times as //! necessary. use core::sync::atomic::{AtomicU32, Ordering}; static RESET_REASON: AtomicU32 = AtomicU32::new(0); /// Reads the most recent reset reason from the Core Mode Controller /// (CMC). pub fn reset_reason() -> ResetReasonRaw { let regs = crate::pac::CMC; let reason = critical_section::with(|_| { let mut r = RESET_REASON.load(Ordering::Relaxed); if r == 0 { // Read status r = regs.srs().read().0; // Clear status regs.ssrs().modify(|w| w.0 = r); RESET_REASON.store(r, Ordering::Relaxed); } r }); ResetReasonRaw(reason) } /// Raw reset reason bits. Can be queried or all reasons can be iterated over #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Copy, Clone, Debug)] pub struct ResetReasonRaw(u32); #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Copy, Clone, Debug)] pub struct ResetReasonRawIter(u32); impl IntoIterator for ResetReasonRaw { type Item = ResetReason; type IntoIter = ResetReasonRawIter; /// Convert to an iterator of contained reset reasons fn into_iter(self) -> Self::IntoIter { ResetReasonRawIter(self.0) } } impl ResetReasonRaw { const MAP: &[(u32, ResetReason)] = &[ (1 << 0, ResetReason::WakeUp), (1 << 1, ResetReason::Por), (1 << 2, ResetReason::VoltageDetect), (1 << 4, ResetReason::Warm), (1 << 5, ResetReason::Fatal), (1 << 8, ResetReason::Pin), (1 << 9, ResetReason::Dap), (1 << 10, ResetReason::ResetAckTimeout), (1 << 11, ResetReason::LowPowerAckTimeout), (1 << 12, ResetReason::SystemClockGeneration), (1 << 13, ResetReason::Wwdt0), (1 << 14, ResetReason::Software), (1 << 15, ResetReason::Lockup), #[cfg(feature = "mcxa5xx")] (1 << 25, ResetReason::Wwdt1), (1 << 26, ResetReason::Cdog0), (1 << 27, ResetReason::Cdog1), (1 << 28, ResetReason::Jtag), #[cfg(feature = "mcxa5xx")] (1 << 30, ResetReason::SecurityViolation), ]; /// Wake up #[inline] pub fn is_wakeup(&self) -> bool { (self.0 & (1 << 0)) != 0 } /// Power-on Reset #[inline] pub fn is_por(&self) -> bool { (self.0 & (1 << 1)) != 0 } /// Voltage detect #[inline] pub fn is_voltage_detect(&self) -> bool { (self.0 & (1 << 2)) != 0 } /// Warm #[inline] pub fn is_warm(&self) -> bool { (self.0 & (1 << 4)) != 0 } /// Fatal #[inline] pub fn is_fatal(&self) -> bool { (self.0 & (1 << 5)) != 0 } /// Pin #[inline] pub fn is_pin(&self) -> bool { (self.0 & (1 << 8)) != 0 } /// DAP #[inline] pub fn is_dap(&self) -> bool { (self.0 & (1 << 9)) != 0 } /// Reset ack timeout #[inline] pub fn is_reset_ack_timeout(&self) -> bool { (self.0 & (1 << 10)) != 0 } /// Low power ack timeout #[inline] pub fn is_low_power_ack_timeout(&self) -> bool { (self.0 & (1 << 11)) != 0 } /// System clock generation #[inline] pub fn is_system_clock_generation(&self) -> bool { (self.0 & (1 << 12)) != 0 } /// Watchdog 0 #[inline] pub fn is_watchdog0(&self) -> bool { (self.0 & (1 << 13)) != 0 } /// Software pub fn is_software(&self) -> bool { (self.0 & (1 << 14)) != 0 } /// Lockup pub fn is_lockup(&self) -> bool { (self.0 & (1 << 15)) != 0 } /// Watchdog 1 #[inline] #[cfg(feature = "mcxa5xx")] pub fn is_watchdog1(&self) -> bool { (self.0 & (1 << 25)) != 0 } /// Code watchdog 0 pub fn is_code_watchdog0(&self) -> bool { (self.0 & (1 << 26)) != 0 } /// Code watchdog 1 pub fn is_code_watchdog1(&self) -> bool { (self.0 & (1 << 27)) != 0 } /// JTAG pub fn is_jtag(&self) -> bool { (self.0 & (1 << 28)) != 0 } /// Security Violation #[cfg(feature = "mcxa5xx")] pub fn is_security_violation(&self) -> bool { (self.0 & (1 << 30)) != 0 } } impl Iterator for ResetReasonRawIter { type Item = ResetReason; fn next(&mut self) -> Option { if self.0 == 0 { return None; } for (mask, var) in ResetReasonRaw::MAP { // If the bit is set... if self.0 & mask != 0 { // clear the bit self.0 &= !mask; // and return the answer return Some(*var); } } // Shouldn't happen, but oh well. None } } /// Indicates the type and source of the most recent reset. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum ResetReason { /// Tamper reset. Tamper, /// JTAG System Reset request. Jtag, /// Code Watchdog 0 reset. Cdog0, /// Code Watchdog 1 reset. Cdog1, /// Lockup reset. Lockup, /// Software reset. Software, /// Windowed Watchdog 0 reset. Wwdt0, /// Windowed Watchdog 1 reset. #[cfg(feature = "mcxa5xx")] Wwdt1, /// Security Violation reset. #[cfg(feature = "mcxa5xx")] SecurityViolation, /// System clock generation reset. SystemClockGeneration, /// Low Power Acknowledge Timeout reset. LowPowerAckTimeout, /// Reset Timeout. ResetAckTimeout, /// Debug Access Port reset. Dap, /// External assertion of RESET_b pin. Pin, /// Fatal reset. Fatal, /// Warm reset. Warm, /// Voltage detect reset. VoltageDetect, /// Power-on reset. Por, /// Wake-up reset. WakeUp, } ================================================ FILE: embassy-mcxa/src/rtc/mcxa2xx.rs ================================================ //! RTC DateTime driver. use core::convert::Infallible; use core::marker::PhantomData; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitCell; use crate::clocks::{WakeGuard, with_clocks}; use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac; use crate::pac::rtc::vals::{Swr, Tcr, Um}; /// RTC interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } trait SealedInstance { fn info() -> &'static Info; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); } /// Trait for RTC peripheral instances #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { type Interrupt: Interrupt; } struct Info { regs: pac::rtc::Rtc, wait_cell: WaitCell, } impl Info { #[inline(always)] fn regs(&self) -> pac::rtc::Rtc { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } unsafe impl Sync for Info {} impl SealedInstance for crate::peripherals::RTC0 { #[inline(always)] fn info() -> &'static Info { static INFO: Info = Info { regs: pac::RTC0, wait_cell: WaitCell::new(), }; &INFO } const PERF_INT_INCR: fn() = crate::perf_counters::incr_interrupt_rtc0; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::incr_interrupt_rtc0_wake; } impl Instance for crate::peripherals::RTC0 { type Interrupt = crate::interrupt::typelevel::RTC; } /// Number of days in a standard year const DAYS_IN_A_YEAR: u32 = 365; /// Number of seconds in a day const SECONDS_IN_A_DAY: u32 = 86400; /// Number of seconds in an hour const SECONDS_IN_A_HOUR: u32 = 3600; /// Number of seconds in a minute const SECONDS_IN_A_MINUTE: u32 = 60; /// Unix epoch start year const YEAR_RANGE_START: u16 = 1970; /// Date and time structure for RTC operations #[derive(Debug, Clone, Copy)] pub struct DateTime { pub year: u16, pub month: u8, pub day: u8, pub hour: u8, pub minute: u8, pub second: u8, } #[derive(Copy, Clone)] pub struct Config { #[allow(dead_code)] wakeup_select: bool, update_mode: Um, #[allow(dead_code)] supervisor_access: bool, compensation_interval: u8, compensation_time: Tcr, } impl Default for Config { fn default() -> Self { Self { wakeup_select: false, update_mode: Um::UM_0, supervisor_access: false, compensation_interval: 0, compensation_time: Tcr::TCR_0, } } } /// RTC interrupt enable flags #[derive(Copy, Clone)] pub struct RtcInterruptEnable; impl RtcInterruptEnable { pub const RTC_TIME_INVALID_INTERRUPT_ENABLE: u32 = 1 << 0; pub const RTC_TIME_OVERFLOW_INTERRUPT_ENABLE: u32 = 1 << 1; pub const RTC_ALARM_INTERRUPT_ENABLE: u32 = 1 << 2; pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; } /// Converts a DateTime structure to Unix timestamp (seconds since 1970-01-01) /// /// # Arguments /// /// * `datetime` - The date and time to convert /// /// # Returns /// /// Unix timestamp as u32 /// /// # Note /// /// This function handles leap years correctly. pub fn convert_datetime_to_seconds(datetime: &DateTime) -> u32 { let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; let mut seconds = (datetime.year as u32 - 1970) * DAYS_IN_A_YEAR; seconds += (datetime.year as u32 / 4) - (1970 / 4); seconds += month_days[datetime.month as usize] as u32; seconds += datetime.day as u32 - 1; if (datetime.year & 3 == 0) && (datetime.month <= 2) { seconds -= 1; } seconds = seconds * SECONDS_IN_A_DAY + (datetime.hour as u32 * SECONDS_IN_A_HOUR) + (datetime.minute as u32 * SECONDS_IN_A_MINUTE) + datetime.second as u32; seconds } /// Converts Unix timestamp to DateTime structure /// /// # Arguments /// /// * `seconds` - Unix timestamp (seconds since 1970-01-01) /// /// # Returns /// /// DateTime structure with the converted date and time /// /// # Note /// /// This function handles leap years correctly. pub fn convert_seconds_to_datetime(seconds: u32) -> DateTime { let mut seconds_remaining = seconds; let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; seconds_remaining %= SECONDS_IN_A_DAY; let hour = (seconds_remaining / SECONDS_IN_A_HOUR) as u8; seconds_remaining %= SECONDS_IN_A_HOUR; let minute = (seconds_remaining / SECONDS_IN_A_MINUTE) as u8; let second = (seconds_remaining % SECONDS_IN_A_MINUTE) as u8; let mut year = YEAR_RANGE_START; let mut days_in_year = DAYS_IN_A_YEAR; while days > days_in_year { days -= days_in_year; year += 1; days_in_year = if year.is_multiple_of(4) { DAYS_IN_A_YEAR + 1 } else { DAYS_IN_A_YEAR }; } let days_per_month = [ 31, if (year.is_multiple_of(4) && !year.is_multiple_of(100)) || year.is_multiple_of(400) { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ]; let mut month = 1; for (m, month_days) in days_per_month.iter().enumerate() { let m = m + 1; if days <= *month_days as u32 { month = m; break; } else { days -= *month_days as u32; } } let day = days as u8; DateTime { year, month: month as u8, day, hour, minute, second, } } /// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) pub struct Rtc<'a> { _inst: core::marker::PhantomData<&'a mut ()>, info: &'static Info, _wg: Option, } impl<'a> Rtc<'a> { /// Create a new instance of the real time clock. pub fn new( _inst: Peri<'a, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'a, config: Config, ) -> Self { let info = T::info(); // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock // on the vsys domain is active let clocks = with_clocks(|c| c.clk_16k_vsys.clone()); let clk = match clocks { None => panic!("Clocks have not been initialized"), Some(None) => panic!("Clocks initialized, but clk_16k_vsys not active"), Some(Some(clk)) => clk, }; let mut inst = Self { info, _inst: PhantomData, _wg: WakeGuard::for_power(&clk.power), }; inst.set_configuration(&config); // Enable RTC interrupt T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self { _inst: core::marker::PhantomData, info, _wg: WakeGuard::for_power(&clk.power), } } fn set_configuration(&mut self, config: &Config) { self.info.regs().cr().modify(|w| w.set_swr(Swr::SWR_1)); self.info.regs().cr().modify(|w| w.set_swr(Swr::SWR_0)); self.info.regs().tsr().write(|w| w.0 = 1); self.info.regs().cr().modify(|w| w.set_um(config.update_mode)); self.info.regs().tcr().modify(|w| { w.set_cir(config.compensation_interval); w.set_tcr(config.compensation_time); }); } /// Set the current date and time /// /// # Arguments /// /// * `datetime` - The date and time to set /// /// # Note /// /// The datetime is converted to Unix timestamp and written to the time seconds register. pub fn set_datetime(&self, datetime: DateTime) { let seconds = convert_datetime_to_seconds(&datetime); self.info.regs().tsr().write(|w| w.0 = seconds); } /// Get the current date and time /// /// # Returns /// /// Current date and time as DateTime /// /// # Note /// /// Reads the current Unix timestamp from the time seconds register and converts it. pub fn get_datetime(&self) -> DateTime { let seconds = self.info.regs().tsr().read().0; convert_seconds_to_datetime(seconds) } /// Set the alarm date and time /// /// # Arguments /// /// * `alarm` - The date and time when the alarm should trigger /// /// # Note /// /// This function: /// - Clears any existing alarm by writing 0 to the alarm register /// - Waits for the clear operation to complete /// - Sets the new alarm time /// - Waits for the write operation to complete /// - Uses timeouts to prevent infinite loops /// - Enables the alarm interrupt after setting pub fn set_alarm(&self, alarm: DateTime) { let seconds = convert_datetime_to_seconds(&alarm); self.info.regs().tar().write(|w| w.0 = 0); let mut timeout = 10000; while self.info.regs().tar().read().0 != 0 && timeout > 0 { timeout -= 1; } self.info.regs().tar().write(|w| w.0 = seconds); let mut timeout = 10000; while self.info.regs().tar().read().0 != seconds && timeout > 0 { timeout -= 1; } self.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); } /// Get the current alarm date and time /// /// # Returns /// /// Alarm date and time as DateTime /// /// # Note /// /// Reads the alarm timestamp from the time alarm register and converts it. pub fn get_alarm(&self) -> DateTime { let alarm_seconds = self.info.regs().tar().read().0; convert_seconds_to_datetime(alarm_seconds) } /// Start the RTC time counter /// /// # Note /// /// Sets the Time Counter Enable (TCE) bit in the status register. pub fn start(&self) { self.info.regs().sr().modify(|w| w.set_tce(true)); } /// Stop the RTC time counter /// /// # Note /// /// Clears the Time Counter Enable (TCE) bit in the status register. pub fn stop(&self) { self.info.regs().sr().modify(|w| w.set_tce(false)); } /// Enable specific RTC interrupts /// /// # Arguments /// /// * `mask` - Bitmask of interrupts to enable (use RtcInterruptEnable constants) /// /// # Note /// /// This function enables the specified interrupt types and resets the alarm occurred flag. /// Available interrupts: /// - Time Invalid Interrupt /// - Time Overflow Interrupt /// - Alarm Interrupt /// - Seconds Interrupt pub fn set_interrupt(&self, mask: u32) { if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_tiie(true)); } if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_toie(true)); } if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_taie(true)); } if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_tsie(true)); } } /// Disable specific RTC interrupts /// /// # Arguments /// /// * `mask` - Bitmask of interrupts to disable (use RtcInterruptEnable constants) /// /// # Note /// /// This function disables the specified interrupt types. pub fn disable_interrupt(&self, mask: u32) { if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_tiie(false)); } if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_toie(false)); } if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_taie(false)); } if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { self.info.regs().ier().modify(|w| w.set_tsie(false)); } } /// Clear the alarm interrupt flag /// /// # Note /// /// This function clears the Time Alarm Interrupt Enable bit. pub fn clear_alarm_flag(&self) { self.info.regs().ier().modify(|w| w.set_taie(false)); } /// Wait for an RTC alarm to trigger. /// /// # Arguments /// /// * `alarm` - The date and time when the alarm should trigger /// /// This function will wait until the RTC alarm is triggered. /// If no alarm is scheduled, it will wait indefinitely until one is scheduled and triggered. pub async fn wait_for_alarm(&mut self, alarm: DateTime) { let wait = self.info.wait_cell().subscribe().await; self.set_alarm(alarm); self.start(); // REVISIT: propagate error? let _ = wait.await; // Clear the interrupt and disable the alarm after waking up self.disable_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); } } /// RTC interrupt handler /// /// This struct implements the interrupt handler for RTC events. impl Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); let rtc = pac::RTC0; // Check if this is actually a time alarm interrupt let sr = rtc.sr().read(); if sr.taf() { rtc.ier().modify(|w| w.set_taie(false)); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } impl<'a> SetConfig for Rtc<'a> { type Config = Config; type ConfigError = Infallible; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_configuration(config); Ok(()) } } ================================================ FILE: embassy-mcxa/src/rtc/mcxa5xx.rs ================================================ //! RTC DateTime driver. use core::marker::PhantomData; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitCell; use crate::clocks::{WakeGuard, with_clocks}; use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac; use crate::pac::rtc::vals::Swr; /// RTC interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } trait SealedInstance { fn info() -> &'static Info; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); } /// Trait for RTC peripheral instances #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { type Interrupt: Interrupt; } struct Info { regs: pac::rtc::Rtc, wait_cell: WaitCell, } impl Info { #[inline(always)] fn regs(&self) -> pac::rtc::Rtc { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } unsafe impl Sync for Info {} impl SealedInstance for crate::peripherals::RTC0 { #[inline(always)] fn info() -> &'static Info { static INFO: Info = Info { regs: pac::RTC0, wait_cell: WaitCell::new(), }; &INFO } const PERF_INT_INCR: fn() = crate::perf_counters::incr_interrupt_rtc0; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::incr_interrupt_rtc0_wake; } impl Instance for crate::peripherals::RTC0 { type Interrupt = crate::interrupt::typelevel::RTC; } /// Month #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Month { /// January January = 1, /// February February = 2, /// March March = 3, /// April April = 4, /// May May = 5, /// June June = 6, /// July July = 7, /// August August = 8, /// September September = 9, /// October October = 10, /// November November = 11, /// December December = 12, } impl From for Month { fn from(value: u8) -> Self { match value { 1 => Self::January, 2 => Self::February, 3 => Self::March, 4 => Self::April, 5 => Self::May, 6 => Self::June, 7 => Self::July, 8 => Self::August, 9 => Self::September, 10 => Self::October, 11 => Self::November, 12 => Self::December, _ => unreachable!(), } } } impl From for u8 { fn from(value: Month) -> Self { value as u8 } } /// Day of the week #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Weekday { /// Sunday Sunday = 0, /// Monday Monday = 1, /// Tuesday Tuesday = 2, /// Wednesday Wednesday = 3, /// Thursday Thursday = 4, /// Friday Friday = 5, /// Saturday Saturday = 6, } impl From for Weekday { fn from(value: u8) -> Self { match value { 0 => Self::Sunday, 1 => Self::Monday, 2 => Self::Tuesday, 3 => Self::Wednesday, 4 => Self::Thursday, 5 => Self::Friday, 6 => Self::Saturday, _ => unreachable!(), } } } impl From for u8 { fn from(value: Weekday) -> Self { value as u8 } } /// Date and time structure for RTC operations #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DateTime { /// Year pub year: i16, /// Month pub month: Month, /// Day pub day: u8, /// Day of the week pub dow: Weekday, /// Hour pub hour: u8, /// Minute pub minute: u8, /// Second pub second: u8, } #[derive(Copy, Clone, Default, PartialEq, PartialOrd)] #[repr(u8)] pub enum Clkout { /// No output clock #[default] None = 0, /// Fine 1Hz clock with both precise edges Fine = 1, /// 32768Hz or 16384Hz output Main = 2, /// Coarse 1Hz clock with both precise edges Coarse = 3, } impl From for u8 { fn from(value: Clkout) -> Self { value as u8 } } #[derive(Copy, Clone, Default, PartialEq, PartialOrd)] pub enum ClkSel { /// 16384Hz #[default] Clk16384, /// 32768Hz Clk32768, } impl From for bool { fn from(value: ClkSel) -> Self { match value { ClkSel::Clk16384 => false, ClkSel::Clk32768 => true, } } } #[derive(Copy, Clone, Default, PartialEq, PartialOrd)] pub enum EnableDaylightSavings { /// No #[default] No, /// Yes Yes, } impl From for bool { fn from(value: EnableDaylightSavings) -> Self { match value { EnableDaylightSavings::No => false, EnableDaylightSavings::Yes => true, } } } #[derive(Copy, Clone, Default, PartialEq, PartialOrd)] pub enum Compensation { /// No compensation #[default] None, /// Coarse compensation Coarse { /// Duration in seconds over which the correction is applied. interval: u8, /// Correction value in terms of number of clock /// cycles of the RTC oscillator clock. correction: i8, }, /// Fine compensation Fine { /// Integral correction value in terms of number of clock /// cycles of the RTC oscillator clock. integral: i8, /// Fractional correction value in terms of number of clock /// cycles of the fixed IRC clock fractional: u8, }, } #[derive(Copy, Clone, Default, PartialEq, PartialOrd)] #[repr(u8)] pub enum AlarmMatch { /// Alarm matches second, minute, and hour. #[default] Hour = 0, /// Alarm matches second, minute, hour, and day. Day = 1, /// Alarm matches second, minute, hour, day, and month. Month = 2, /// Alarm matches second, minute, hour, day, month, and year. Year = 3, } impl From for u8 { fn from(value: AlarmMatch) -> Self { value as u8 } } #[derive(Copy, Clone, Default)] pub struct Config { /// Clkout selection clkout: Clkout, /// RTC Clock select clksel: ClkSel, /// Daylight savings select daylight_savings: EnableDaylightSavings, /// Alarm match. Selects which time and calendar counters are used /// for matching and will generate and alarm. alarm_match: AlarmMatch, /// Compensation method compensation: Compensation, } /// Errors exclusive to HW initialization #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum SetupError { /// Clock configuration error. ClockSetup, } /// Errors exclusive for datetime. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum RtcError { /// Invalid datetime InvalidDateTime, /// Invalid DST year InvalidDstYear, /// Other error Other, } /// RTC driver. pub struct Rtc<'a> { _inst: core::marker::PhantomData<&'a mut ()>, info: &'static Info, _wg: Option, } impl<'a> Rtc<'a> { const BASE_YEAR: i16 = 2112; /// Create a new instance of the real time clock. pub fn new( _inst: Peri<'a, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'a, config: Config, ) -> Result { let info = T::info(); // The RTC is NOT gated by the MRCC, but we DO need to make // sure either the 16k clock or the 32k clock is active. let clocks = if config.clksel == ClkSel::Clk16384 { with_clocks(|c| c.clk_16k_vsys.clone()) } else { with_clocks(|c| c.clk_32k_vsys.clone()) }; let clk = clocks.flatten().ok_or(SetupError::ClockSetup)?; let mut inst = Self { info, _inst: PhantomData, _wg: WakeGuard::for_power(&clk.power), }; inst.set_configuration(&config)?; // Enable RTC interrupt T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Ok(inst) } fn set_configuration(&mut self, config: &Config) -> Result<(), SetupError> { self.disable_write_protect(); self.info.regs().ctrl().modify(|w| w.set_swr(Swr::ASSERTED)); self.info.regs().ctrl().modify(|w| w.set_swr(Swr::CLEARED)); self.info.regs().ctrl().modify(|w| { w.set_clkout(config.clkout.into()); w.set_clko_dis(config.clkout == Clkout::None); w.set_clk_sel(config.clksel.into()); w.set_dst_en(config.daylight_savings.into()); w.set_alm_match(config.alarm_match.into()); match config.compensation { Compensation::None => { w.set_comp_en(false); w.set_fineen(false); } Compensation::Coarse { .. } => { w.set_comp_en(true); w.set_fineen(false); } Compensation::Fine { .. } => { w.set_comp_en(false); w.set_fineen(true); } } }); self.info.regs().compen().write(|w| match config.compensation { Compensation::None => {} Compensation::Coarse { interval, correction } => { w.set_compen_val((interval as u16) << 8 | correction as u16); } Compensation::Fine { integral, fractional } => { w.set_compen_val((integral as u16) << 8 | fractional as u16); } }); self.enable_write_protect(); Ok(()) } fn disable_write_protect(&mut self) { self.info.regs().status8().write_value(0b00); self.info.regs().status8().write_value(0b01); self.info.regs().status8().write_value(0b11); self.info.regs().status8().write_value(0b10); } fn enable_write_protect(&mut self) { self.info.regs().status8().write_value(0b10); } fn is_valid_datetime(&self, t: DateTime) -> Result<(), RtcError> { if (t.year < (Self::BASE_YEAR - 128) || t.year > (Self::BASE_YEAR + 127)) || t.day > 31 || t.hour > 23 || t.minute > 59 || t.second > 59 { Err(RtcError::InvalidDateTime) } else { Ok(()) } } fn is_valid_dst_year(&self, t: DateTime) -> Result<(), RtcError> { let now = self.now()?; if now.year != t.year { Err(RtcError::InvalidDstYear) } else { Ok(()) } } /// Return the current datetime. /// /// Will block until we can access Datetime registers. pub fn now(&self) -> Result { let ym = self.info.regs().yearmon().read(); let d = self.info.regs().days().read(); let hm = self.info.regs().hourmin().read(); let second = self.info.regs().seconds().read().sec_cnt(); let year = (i16::from(ym.yrofst() as i8) + Self::BASE_YEAR).into(); let month = ym.mon_cnt().into(); let dow = d.dow().into(); let day = d.day_cnt(); let hour = hm.hour_cnt(); let minute = hm.min_cnt(); Ok(DateTime { year, month, day, dow, hour, minute, second, }) } /// 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.is_valid_datetime(t)?; let year = (t.year - Self::BASE_YEAR) as u8; let month = t.month.into(); let dow = t.dow.into(); let day = t.day; let hour = t.hour; let minute = t.minute; let second = t.second; self.disable_write_protect(); self.info.regs().yearmon().write(|w| { w.set_yrofst(year); w.set_mon_cnt(month); }); self.info.regs().days().write(|w| { w.set_dow(dow); w.set_day_cnt(day); }); self.info.regs().hourmin().write(|w| { w.set_hour_cnt(hour); w.set_min_cnt(minute); }); self.info.regs().seconds().write(|w| w.set_sec_cnt(second)); self.enable_write_protect(); Ok(()) } /// Set the Daylight Savings start and end time. /// /// Note: only day, month, and hour are accounted for. The /// underlying HW is incapable of enabling DST for a future year. /// /// # Errors /// /// `RtcError::InvalidDateTime` if the either datetime is not a valid range. /// `RtcError::InvalidDstYear` if the either datetime contains a future or past year. pub fn set_dst(&mut self, start: DateTime, end: DateTime) -> Result<(), RtcError> { self.is_valid_datetime(start)?; self.is_valid_datetime(end)?; self.is_valid_dst_year(start)?; self.is_valid_dst_year(end)?; self.disable_write_protect(); self.info.regs().dst_hour().write(|w| { w.set_dst_start_hour(start.hour - 1); w.set_dst_end_hour(end.hour - 1); }); self.info.regs().dst_month().write(|w| { w.set_dst_start_month(start.month.into()); w.set_dst_end_month(end.month.into()); }); self.info.regs().dst_day().write(|w| { w.set_dst_start_day(start.day); w.set_dst_end_day(end.day); }); self.enable_write_protect(); Ok(()) } /// Set alarm to `t` and wait for the RTC alarm interrup to trigger. /// /// # Errors /// /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. pub async fn wait_for_alarm(&mut self, t: DateTime) -> Result<(), RtcError> { self.is_valid_datetime(t)?; let year = (t.year - Self::BASE_YEAR) as u8; let month = t.month.into(); let day = t.day; let hour = t.hour; let minute = t.minute; let second = t.second; self.disable_write_protect(); self.info.regs().alm_yearmon().write(|w| { w.set_alm_year(year); w.set_alm_mon(month); }); self.info.regs().alm_days().write(|w| { w.set_alm_day(day); }); self.info.regs().alm_hourmin().write(|w| { w.set_alm_hour(hour); w.set_alm_min(minute); }); self.info.regs().alm_seconds().write(|w| w.set_alm_sec(second)); self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| w.set_alm_ie(true)); self.info.regs().isr().read().alm_is() }) .await .map_err(|_| RtcError::Other)?; self.info.regs().isr().write(|w| w.set_alm_is(true)); self.enable_write_protect(); Ok(()) } } /// RTC interrupt handler /// /// This struct implements the interrupt handler for RTC events. impl Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); // Check if this is actually a time alarm interrupt let status = T::info().regs().isr().read(); if status.alm_is() { T::info().regs().ier().modify(|w| w.set_alm_ie(false)); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } impl<'a> SetConfig for Rtc<'a> { type Config = Config; type ConfigError = SetupError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_configuration(config) } } ================================================ FILE: embassy-mcxa/src/rtc/mod.rs ================================================ //! RTC DateTime driver. #[cfg(feature = "mcxa2xx")] mod mcxa2xx; #[cfg(feature = "mcxa2xx")] pub use mcxa2xx::{Config, DateTime, InterruptHandler, Rtc}; #[cfg(feature = "mcxa5xx")] mod mcxa5xx; #[cfg(feature = "mcxa5xx")] pub use mcxa5xx::{Compensation, Config, DateTime, InterruptHandler, Month, Rtc, Weekday}; ================================================ FILE: embassy-mcxa/src/spi/controller.rs ================================================ //! LPSPI Controller Driver. use core::marker::PhantomData; use core::sync::atomic::{Ordering, fence}; use embassy_embedded_hal::SetConfig; use embassy_futures::join::join; use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; pub use embedded_hal_1::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode, Phase, Polarity}; use nxp_pac::lpspi::vals::{Cpha, Cpol, Lsbf, Master, Mbf, Outcfg, Pcspol, Pincfg, Prescale, Rrf, Rtf, Rxmsk, Txmsk}; use super::{Async, AsyncMode, Blocking, Dma, Info, Instance, MisoPin, Mode as IoMode, MosiPin, SckPin}; use crate::clocks::periph_helpers::{Div4, LpspiClockSel, LpspiConfig}; use crate::clocks::{ClockError, PoweredClock, WakeGuard, enable_and_reset}; use crate::dma::{Channel, DMA_MAX_TRANSFER_SIZE, DmaChannel, TransferOptions}; use crate::gpio::AnyPin; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; // LPSPI has a 4-word FIFO. const LPSPI_FIFO_SIZE: u8 = 4; /// Errors exclusive to HW initialization #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum SetupError { /// Clock configuration error. ClockSetup(ClockError), /// Other internal errors or unexpected state. Other, } /// I/O Errors #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum IoError { /// Receive error. /// /// Indicates FIFO overflow condition has happened. ReceiveError, /// Transmit error. /// /// Indicated FIFO underrun condition has happened. TransmitError, /// Other internal errors or unexpected state. Other, } impl From for IoError { fn from(_value: crate::dma::InvalidParameters) -> Self { IoError::Other } } /// SPI interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); let r = T::info().regs().ier().read().0; if r != 0 { T::info().regs().ier().write(|w| w.0 = 0); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } /// SPI target clock configuration #[derive(Clone)] #[non_exhaustive] pub struct ClockConfig { /// Powered clock configuration pub power: PoweredClock, /// LPSPI clock source pub source: LpspiClockSel, /// LPSPI pre-divider pub div: Div4, } impl Default for ClockConfig { fn default() -> Self { Self { power: PoweredClock::NormalEnabledDeepSleepDisabled, source: LpspiClockSel::FroLfDiv, div: const { Div4::no_div() }, } } } /// SPI bit order #[derive(Default)] pub enum BitOrder { /// Most-significant bit first #[default] MsbFirst, /// Least-significant bit first LsbFirst, } /// SPI controller configuration #[non_exhaustive] pub struct Config { /// Frequency in Hertz. pub frequency: u32, /// SPI operating mode. pub mode: Mode, /// Bit order pub bit_order: BitOrder, /// Clock configuration pub clock_config: ClockConfig, } impl Default for Config { fn default() -> Self { Self { frequency: 1_000_000, mode: MODE_0, bit_order: Default::default(), clock_config: Default::default(), } } } /// Spi driver. pub struct Spi<'d, M: IoMode> { info: &'static Info, _sck: Peri<'d, AnyPin>, _miso: Option>, _mosi: Option>, _freq: u32, mode: M, _wg: Option, _phantom: PhantomData<&'d M>, } impl<'d, M: IoMode> Spi<'d, M> { fn new_inner( _peri: Peri<'d, T>, _sck: Peri<'d, AnyPin>, _mosi: Option>, _miso: Option>, config: Config, mode: M, ) -> Result { let ClockConfig { power, source, div } = config.clock_config; // Enable clocks let conf = LpspiConfig { power, source, div, instance: T::CLOCK_INSTANCE, }; let parts = unsafe { enable_and_reset::(&conf).map_err(SetupError::ClockSetup)? }; let mut inst = Self { info: T::info(), _sck, _mosi, _miso, mode, _freq: parts.freq, _wg: parts.wake_guard, _phantom: PhantomData, }; inst.set_configuration(&config)?; Ok(inst) } fn set_configuration(&mut self, config: &Config) -> Result<(), SetupError> { let (prescaler, div) = compute_baud_params(self._freq, config.frequency); self.info.regs().cr().write(|w| { w.set_men(false); w.set_rst(true); w.set_rtf(Rtf::TXFIFO_RST); w.set_rrf(Rrf::RXFIFO_RST); }); self.info.regs().cr().modify(|w| w.set_rst(false)); self.info.regs().cfgr1().write(|w| { w.set_master(Master::MASTER_MODE); w.set_pincfg(Pincfg::SIN_IN_SOUT_OUT); w.set_pcspol(Pcspol::DISCARDED); }); self.info.regs().ccr().write(|w| { w.set_sckdiv(div); w.set_dbt(div); w.set_pcssck(div); w.set_sckpcs(div); }); self.info.regs().fcr().write(|w| { w.set_txwater(0); w.set_rxwater(0); }); self.info.regs().tcr().write(|w| { // Assuming byte transfers w.set_framesz(7); w.set_cpol(match config.mode.polarity { Polarity::IdleLow => Cpol::INACTIVE_LOW, Polarity::IdleHigh => Cpol::INACTIVE_HIGH, }); w.set_cpha(match config.mode.phase { Phase::CaptureOnFirstTransition => Cpha::CAPTURED, Phase::CaptureOnSecondTransition => Cpha::CHANGED, }); w.set_lsbf(match config.bit_order { BitOrder::MsbFirst => Lsbf::MSB_FIRST, BitOrder::LsbFirst => Lsbf::LSB_FIRST, }); w.set_prescale(prescaler); }); self.info.regs().cr().modify(|w| w.set_men(true)); Ok(()) } } impl<'d, M: IoMode> Spi<'d, M> { fn check_status(&mut self) -> Result<(), IoError> { let status = self.info.regs().sr().read(); if status.ref_() { // Empty the RX FIFO. self.info.regs().cr().modify(|w| w.set_rrf(Rrf::RXFIFO_RST)); self.info.regs().sr().write(|w| w.set_ref_(true)); Err(IoError::ReceiveError) } else if status.tef() { self.info.regs().sr().write(|w| w.set_tef(true)); Err(IoError::TransmitError) } else { Ok(()) } } /// Read data from Spi blocking execution until done. pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::MASK); w.set_rxmsk(Rxmsk::NORMAL); }); for word in data { // Wait until we have data in the RxFIFO. while self.info.regs().fsr().read().rxcount() == 0 {} self.check_status()?; *word = self.info.regs().rdr().read().data() as u8; } Ok(()) } /// Write data to Spi blocking execution until done. pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::MASK); }); let fifo_size = LPSPI_FIFO_SIZE; for word in data { // Wait until we have at least one byte space in the TxFIFO. while self.info.regs().fsr().read().txcount() - fifo_size == 0 {} self.check_status()?; self.info.regs().tdr().write(|w| w.set_data(*word as u32)); } self.blocking_flush() } /// Transfer data to SPI blocking execution until done. pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), IoError> { if read.is_empty() && write.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); let fifo_size = LPSPI_FIFO_SIZE; for (wb, rb) in write.iter().zip(read.iter_mut()) { // Wait until we have at least one byte space in the TxFIFO. while self.info.regs().fsr().read().txcount() - fifo_size == 0 {} self.check_status()?; self.info.regs().tdr().write(|w| w.set_data(*wb as u32)); // Wait until we have data in the RxFIFO. while self.info.regs().fsr().read().rxcount() == 0 {} self.check_status()?; *rb = self.info.regs().rdr().read().data() as u8; } self.blocking_flush() } /// Transfer data in place to SPI blocking execution until done. pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); let fifo_size = LPSPI_FIFO_SIZE; for word in data { // Wait until we have at least one byte space in the TxFIFO. while self.info.regs().fsr().read().txcount() - fifo_size == 0 {} self.check_status()?; self.info.regs().tdr().write(|w| w.set_data(*word as u32)); // Wait until we have data in the RxFIFO. while self.info.regs().fsr().read().rxcount() == 0 {} self.check_status()?; *word = self.info.regs().rdr().read().data() as u8; } self.blocking_flush() } /// Block execution until Spi is done. pub fn blocking_flush(&mut self) -> Result<(), IoError> { while self.info.regs().sr().read().mbf() == Mbf::BUSY {} self.check_status() } } impl<'d> Spi<'d, Blocking> { /// Create a SPI driver in blocking mode. pub fn new_blocking( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, mosi: Peri<'d, impl MosiPin + 'd>, miso: Peri<'d, impl MisoPin + 'd>, config: Config, ) -> Result { sck.mux(); mosi.mux(); miso.mux(); let sck = sck.into(); let mosi = mosi.into(); let miso = miso.into(); Self::new_inner(_peri, sck, Some(mosi), Some(miso), config, Blocking) } /// Create a TX-only SPI driver in blocking mode. pub fn new_blocking_txonly( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, mosi: Peri<'d, impl MosiPin + 'd>, config: Config, ) -> Result { sck.mux(); mosi.mux(); let sck = sck.into(); let mosi = mosi.into(); Self::new_inner(_peri, sck, Some(mosi), None, config, Blocking) } /// Create an RX-only SPI driver in blocking mode. pub fn new_blocking_rxonly( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, miso: Peri<'d, impl MisoPin + 'd>, config: Config, ) -> Result { sck.mux(); miso.mux(); let sck = sck.into(); let miso = miso.into(); Self::new_inner(_peri, sck, None, Some(miso), config, Blocking) } } impl<'d> Spi<'d, Async> { /// Create a SPI driver in async mode. pub fn new_async( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, mosi: Peri<'d, impl MosiPin + 'd>, miso: Peri<'d, impl MisoPin + 'd>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { sck.mux(); mosi.mux(); miso.mux(); let sck = sck.into(); let mosi = mosi.into(); let miso = miso.into(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_peri, sck, Some(mosi), Some(miso), config, Async) } /// Create a TX-only SPI driver in async mode. pub fn new_async_txonly( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, mosi: Peri<'d, impl MosiPin + 'd>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { sck.mux(); mosi.mux(); let sck = sck.into(); let mosi = mosi.into(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_peri, sck, Some(mosi), None, config, Async) } /// Create an RX-only SPI driver in async mode. pub fn new_async_rxonly( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, miso: Peri<'d, impl MisoPin + 'd>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { sck.mux(); miso.mux(); let sck = sck.into(); let miso = miso.into(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner(_peri, sck, None, Some(miso), config, Async) } } impl<'d> Spi<'d, Dma<'d>> { /// Create a SPI driver in async mode. pub fn new_async_with_dma( _peri: Peri<'d, T>, sck: Peri<'d, impl SckPin + 'd>, mosi: Peri<'d, impl MosiPin + 'd>, miso: Peri<'d, impl MisoPin + 'd>, tx_dma: Peri<'d, impl Channel>, rx_dma: Peri<'d, impl Channel>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { sck.mux(); mosi.mux(); miso.mux(); let sck = sck.into(); let mosi = mosi.into(); let miso = miso.into(); let tx_dma = DmaChannel::new(tx_dma); let rx_dma = DmaChannel::new(rx_dma); // enable this channel's interrupt tx_dma.enable_interrupt(); rx_dma.enable_interrupt(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self::new_inner( _peri, sck, Some(mosi), Some(miso), config, Dma { tx_dma, rx_dma, tx_request: T::TX_DMA_REQUEST, rx_request: T::RX_DMA_REQUEST, }, ) } async fn read_dma_chunk(&mut self, data: &mut [u8]) -> Result<(), IoError> { let rx_peri_addr = self.info.regs().rdr().as_ptr() as *mut u8; let tx_peri_addr = self.info.regs().tdr().as_ptr() as *mut u8; unsafe { // Clean up channel state self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.rx_dma.clear_interrupt(); self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type self.mode.rx_dma.set_request_source(self.mode.rx_request); self.mode.tx_dma.set_request_source(self.mode.tx_request); self.mode.tx_dma.setup_write_zeros_to_peripheral( data.len(), tx_peri_addr, TransferOptions::NO_INTERRUPTS, )?; self.mode.rx_dma.setup_read_from_peripheral( rx_peri_addr, data, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Enable SPI DMA request self.info.regs().der().modify(|w| { w.set_rdde(true); w.set_tdde(true); }); // Enable DMA channel request self.mode.rx_dma.enable_request(); self.mode.tx_dma.enable_request(); } // Wait for completion asynchronously core::future::poll_fn(|cx| { self.mode.rx_dma.waker().register(cx.waker()); if self.mode.rx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Cleanup self.info.regs().der().modify(|w| { w.set_rdde(false); w.set_tdde(false); }); unsafe { self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } // Ensure all writes by DMA are visible to the CPU // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Acquire); Ok(()) } async fn write_dma_chunk(&mut self, data: &[u8]) -> Result<(), IoError> { let peri_addr = self.info.regs().tdr().as_ptr() as *mut u8; unsafe { // Clean up channel state self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type (type-safe) self.mode.tx_dma.set_request_source(self.mode.tx_request); // Configure TCD for memory-to-peripheral transfer self.mode .tx_dma .setup_write_to_peripheral(data, peri_addr, false, TransferOptions::COMPLETE_INTERRUPT)?; // Ensure all writes by CPU are visible to the DMA // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Release); // Enable SPI TX DMA request self.info.regs().der().modify(|w| w.set_tdde(true)); // Enable DMA channel request self.mode.tx_dma.enable_request(); } // Wait for completion asynchronously core::future::poll_fn(|cx| { self.mode.tx_dma.waker().register(cx.waker()); if self.mode.tx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; // Cleanup self.info.regs().der().modify(|w| w.set_tdde(false)); unsafe { self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } Ok(()) } async fn transfer_dma_chunk(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), IoError> { let rx_peri_addr = self.info.regs().rdr().as_ptr() as *mut u8; let tx_peri_addr = self.info.regs().tdr().as_ptr() as *mut u8; unsafe { // Clean up channel state self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.rx_dma.clear_interrupt(); self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type self.mode.rx_dma.set_request_source(self.mode.rx_request); self.mode.tx_dma.set_request_source(self.mode.tx_request); self.mode.tx_dma.setup_write_to_peripheral( write, tx_peri_addr, false, TransferOptions::COMPLETE_INTERRUPT, )?; self.mode.rx_dma.setup_read_from_peripheral( rx_peri_addr, read, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Ensure all writes by CPU are visible to the DMA // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Release); // Enable SPI DMA request self.info.regs().der().modify(|w| { w.set_rdde(true); w.set_tdde(true); }); // Enable DMA channel request self.mode.rx_dma.enable_request(); self.mode.tx_dma.enable_request(); } // Wait for completion asynchronously let tx_transfer = async { core::future::poll_fn(|cx| { self.mode.tx_dma.waker().register(cx.waker()); if self.mode.tx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await; if read.len() > write.len() { let write_bytes_len = read.len() - write.len(); unsafe { self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); self.mode.tx_dma.setup_write_zeros_to_peripheral( write_bytes_len, tx_peri_addr, TransferOptions::COMPLETE_INTERRUPT, )?; self.mode.tx_dma.enable_request(); } core::future::poll_fn(|cx| { self.mode.tx_dma.waker().register(cx.waker()); if self.mode.tx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }) .await } Ok::<(), IoError>(()) }; let rx_transfer = core::future::poll_fn(|cx| { self.mode.rx_dma.waker().register(cx.waker()); if self.mode.rx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }); let (tx_res, ()) = join(tx_transfer, rx_transfer).await; tx_res?; // Cleanup self.info.regs().der().modify(|w| { w.set_rdde(false); w.set_tdde(false); }); unsafe { self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } // if write > read we should clear any overflow of the FIFO SPI buffer if write.len() > read.len() { while self.info.regs().fsr().read().rxcount() == 0 {} self.check_status()?; let _ = self.info.regs().rdr().read().data() as u8; } // Ensure all writes by DMA are visible to the CPU // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Acquire); Ok(()) } async fn transfer_in_place_dma_chunk(&mut self, data: &mut [u8]) -> Result<(), IoError> { let rx_peri_addr = self.info.regs().rdr().as_ptr() as *mut u8; let tx_peri_addr = self.info.regs().tdr().as_ptr() as *mut u8; unsafe { // Clean up channel state self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.rx_dma.clear_interrupt(); self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); self.mode.tx_dma.clear_interrupt(); // Set DMA request source from instance type self.mode.rx_dma.set_request_source(self.mode.rx_request); self.mode.tx_dma.set_request_source(self.mode.tx_request); self.mode.tx_dma.setup_write_to_peripheral( data, tx_peri_addr, false, TransferOptions::COMPLETE_INTERRUPT, )?; self.mode.rx_dma.setup_read_from_peripheral( rx_peri_addr, data, false, TransferOptions::COMPLETE_INTERRUPT, )?; // Ensure all writes by CPU are visible to the DMA // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Release); // Enable SPI DMA request self.info.regs().der().modify(|w| { w.set_rdde(true); w.set_tdde(true); }); // Enable DMA channel request self.mode.rx_dma.enable_request(); self.mode.tx_dma.enable_request(); } // Wait for completion asynchronously let tx_transfer = core::future::poll_fn(|cx| { self.mode.tx_dma.waker().register(cx.waker()); if self.mode.tx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }); let rx_transfer = core::future::poll_fn(|cx| { self.mode.rx_dma.waker().register(cx.waker()); if self.mode.rx_dma.is_done() { core::task::Poll::Ready(()) } else { core::task::Poll::Pending } }); join(tx_transfer, rx_transfer).await; // Cleanup self.info.regs().der().modify(|w| { w.set_rdde(false); w.set_tdde(false); }); unsafe { self.mode.rx_dma.disable_request(); self.mode.rx_dma.clear_done(); self.mode.tx_dma.disable_request(); self.mode.tx_dma.clear_done(); } // Ensure all writes by DMA are visible to the CPU // TODO: ensure this is done internal to the DMA methods so individual drivers // don't need to handle this? fence(Ordering::Acquire); Ok(()) } } trait AsyncEngine { async fn async_read_internal(&mut self, data: &mut [u8]) -> Result<(), IoError>; async fn async_write_internal(&mut self, data: &[u8]) -> Result<(), IoError>; async fn async_transfer_internal(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), IoError>; async fn async_transfer_in_place_internal(&mut self, data: &mut [u8]) -> Result<(), IoError>; } #[allow(private_bounds)] impl<'d, M: AsyncMode> Spi<'d, M> where Self: AsyncEngine, { /// Read data from Spi async execution until done. pub fn async_read(&mut self, data: &mut [u8]) -> impl Future> { ::async_read_internal(self, data) } /// Write data to Spi async execution until done. pub fn async_write(&mut self, data: &[u8]) -> impl Future> { ::async_write_internal(self, data) } /// Transfer data to SPI async execution until done. pub fn async_transfer(&mut self, read: &mut [u8], write: &[u8]) -> impl Future> { ::async_transfer_internal(self, read, write) } /// Transfer data in place to SPI async execution until done. pub fn async_transfer_in_place(&mut self, data: &mut [u8]) -> impl Future> { ::async_transfer_in_place_internal(self, data) } /// Async flush. pub async fn async_flush(&mut self) -> Result<(), IoError> { self.info .wait_cell() .wait_for(|| { self.info.regs().ier().write(|w| w.set_tcie(true)); self.info.regs().sr().read().tcf() }) .await .map_err(|_| IoError::Other) } } impl<'d> AsyncEngine for Spi<'d, Async> { async fn async_read_internal(&mut self, data: &mut [u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::MASK); w.set_rxmsk(Rxmsk::NORMAL); }); for word in data { // Wait until we have data in the RxFIFO. self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| { w.set_rdie(true); w.set_reie(true); }); self.info.regs().fsr().read().rxcount() > 0 || self.info.regs().sr().read().ref_() }) .await .map_err(|_| IoError::Other)?; self.check_status()?; // dummy data self.info.regs().tdr().write(|w| w.set_data(0)); *word = self.info.regs().rdr().read().data() as u8; } Ok(()) } async fn async_write_internal(&mut self, data: &[u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::MASK); }); for word in data { // Wait until we have at least one byte space in the TxFIFO. self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| { w.set_tdie(true); w.set_teie(true); }); self.info.regs().fsr().read().txcount() < LPSPI_FIFO_SIZE || self.info.regs().sr().read().tef() }) .await .map_err(|_| IoError::Other)?; self.check_status()?; self.info.regs().tdr().write(|w| w.set_data(*word as u32)); } self.async_flush().await } async fn async_transfer_internal(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), IoError> { if read.is_empty() && write.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); // Zip will terminate whenever the first of write or read are exhausted for (wb, rb) in write.iter().zip(read.iter_mut()) { // Wait until we have at least one byte space in the TxFIFO. self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| { w.set_tdie(true); w.set_teie(true); }); self.info.regs().fsr().read().txcount() < LPSPI_FIFO_SIZE || self.info.regs().sr().read().tef() }) .await .map_err(|_| IoError::Other)?; self.check_status()?; self.info.regs().tdr().write(|w| w.set_data(*wb as u32)); // Wait until we have data in the RxFIFO. self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| { w.set_rdie(true); w.set_reie(true); }); self.info.regs().fsr().read().rxcount() > 0 || self.info.regs().sr().read().ref_() }) .await .map_err(|_| IoError::Other)?; self.check_status()?; *rb = self.info.regs().rdr().read().data() as u8; } self.async_flush().await } async fn async_transfer_in_place_internal(&mut self, data: &mut [u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); for word in data { // Wait until we have at least one byte space in the TxFIFO. self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| w.set_tdie(true)); self.info.regs().fsr().read().txcount() < LPSPI_FIFO_SIZE }) .await .map_err(|_| IoError::Other)?; self.info.regs().tdr().write(|w| w.set_data(*word as u32)); // Wait until we have data in the RxFIFO. self.info .wait_cell() .wait_for(|| { self.info.regs().ier().modify(|w| w.set_rdie(true)); self.info.regs().fsr().read().rxcount() > 0 }) .await .map_err(|_| IoError::Other)?; *word = self.info.regs().rdr().read().data() as u8; } self.async_flush().await } } impl<'d> AsyncEngine for Spi<'d, Dma<'d>> { async fn async_read_internal(&mut self, data: &mut [u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); self.info.regs().cfgr1().modify(|w| w.set_outcfg(Outcfg::TRISTATED)); let _on_drop = OnDrop::new(|| { self.info.regs().der().modify(|w| w.set_rdde(false)); self.info.regs().cfgr1().modify(|w| w.set_outcfg(Outcfg::TRISTATED)); }); for chunk in data.chunks_mut(DMA_MAX_TRANSFER_SIZE) { self.read_dma_chunk(chunk).await?; } Ok(()) } async fn async_write_internal(&mut self, data: &[u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::MASK); }); let on_drop = OnDrop::new(|| { self.info.regs().der().modify(|w| w.set_tdde(false)); }); for chunk in data.chunks(DMA_MAX_TRANSFER_SIZE) { self.write_dma_chunk(chunk).await?; } on_drop.defuse(); self.async_flush().await } async fn async_transfer_internal(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), IoError> { if read.is_empty() && write.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); let on_drop = OnDrop::new(|| { self.info.regs().der().modify(|w| w.set_tdde(false)); }); for (read_chunk, write_chunk) in read .chunks_mut(DMA_MAX_TRANSFER_SIZE) .zip(write.chunks(DMA_MAX_TRANSFER_SIZE)) { self.transfer_dma_chunk(read_chunk, write_chunk).await?; } on_drop.defuse(); self.async_flush().await } async fn async_transfer_in_place_internal(&mut self, data: &mut [u8]) -> Result<(), IoError> { if data.is_empty() { return Ok(()); } self.info.regs().tcr().modify(|w| { w.set_txmsk(Txmsk::NORMAL); w.set_rxmsk(Rxmsk::NORMAL); }); for chunk in data.chunks_mut(DMA_MAX_TRANSFER_SIZE) { self.transfer_in_place_dma_chunk(chunk).await?; } self.async_flush().await } } /// Compute prescaler and SCKDIV for the desired baud rate. /// Returns (prescaler, sckdiv) where: /// - prescaler is a Prescaler enum value /// - sckdiv is 0-255 /// /// Baud = src_hz / (prescaler.divisor() * (SCKDIV + 2)) pub(super) fn compute_baud_params(src_hz: u32, baud_hz: u32) -> (Prescale, u8) { if baud_hz == 0 { return (Prescale::DIVIDEBY1, 0); } let prescalers = [ Prescale::DIVIDEBY1, Prescale::DIVIDEBY2, Prescale::DIVIDEBY4, Prescale::DIVIDEBY8, Prescale::DIVIDEBY16, Prescale::DIVIDEBY32, Prescale::DIVIDEBY64, Prescale::DIVIDEBY128, ]; let (prescaler, div, _) = prescalers.iter().fold( (Prescale::DIVIDEBY1, 0u8, u32::MAX), |(best_pre, best_div, best_err), &prescaler| { let divisor: u32 = 1 << (prescaler as u8); let denom = divisor.saturating_mul(baud_hz); let sckdiv_calc = (src_hz + denom / 2) / denom; if sckdiv_calc < 2 { return (best_pre, best_div, best_err); } let sckdiv = (sckdiv_calc - 2).min(255) as u8; let actual_baud = src_hz / (divisor * (sckdiv as u32 + 2)); let error = actual_baud.abs_diff(baud_hz); if error < best_err { (prescaler, sckdiv, error) } else { (best_pre, best_div, best_err) } }, ); (prescaler, div) } impl<'d, M: IoMode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, M> { type Error = IoError; fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { self.blocking_transfer_in_place(words)?; Ok(words) } } impl<'d, M: IoMode> embedded_hal_02::blocking::spi::Write for Spi<'d, M> { type Error = IoError; fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { self.blocking_write(words) } } impl embedded_hal_1::spi::Error for IoError { fn kind(&self) -> embedded_hal_1::spi::ErrorKind { match *self { IoError::Other => embedded_hal_1::spi::ErrorKind::Other, IoError::ReceiveError => embedded_hal_1::spi::ErrorKind::Overrun, IoError::TransmitError => embedded_hal_1::spi::ErrorKind::Other, } } } impl<'d, M: IoMode> embedded_hal_1::spi::ErrorType for Spi<'d, M> { type Error = IoError; } impl<'d, M: IoMode> embedded_hal_1::spi::SpiBus for Spi<'d, M> { fn flush(&mut self) -> Result<(), Self::Error> { self.blocking_flush() } fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.blocking_read(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, M: AsyncMode> embedded_hal_async::spi::SpiBus for Spi<'d, M> where Spi<'d, M>: AsyncEngine, { async fn flush(&mut self) -> Result<(), Self::Error> { self.async_flush().await } async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { self.async_write(words).await } async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.async_read(words).await } async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { self.async_transfer(read, write).await } async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.async_transfer_in_place(words).await } } impl<'d, M: IoMode> SetConfig for Spi<'d, M> { type Config = Config; type ConfigError = SetupError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_configuration(config) } } ================================================ FILE: embassy-mcxa/src/spi/mod.rs ================================================ //! LPSPI driver use embassy_hal_internal::PeripheralType; use maitake_sync::WaitCell; use paste::paste; use crate::clocks::Gate; use crate::clocks::periph_helpers::LpspiConfig; use crate::dma::{DmaChannel, DmaRequest}; use crate::gpio::{GpioPin, SealedPin}; use crate::{interrupt, pac}; pub mod controller; mod sealed { /// Seal a trait pub trait Sealed {} } trait SealedInstance: Gate { fn info() -> &'static Info; /// Clock instance const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpspiInstance; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); const TX_DMA_REQUEST: DmaRequest; const RX_DMA_REQUEST: DmaRequest; } /// SPI Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this SPI instance. type Interrupt: interrupt::typelevel::Interrupt; } struct Info { regs: pac::lpspi::Lpspi, wait_cell: WaitCell, } impl Info { #[inline(always)] fn regs(&self) -> pac::lpspi::Lpspi { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } unsafe impl Sync for Info {} macro_rules! impl_instance { ($($n:expr),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], wait_cell: WaitCell::new(), }; &INFO } const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpspiInstance = crate::clocks::periph_helpers::LpspiInstance::[]; const PERF_INT_INCR: fn() = crate::perf_counters::[]; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::[]; const TX_DMA_REQUEST: DmaRequest = DmaRequest::[]; const RX_DMA_REQUEST: DmaRequest = DmaRequest::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0, 1); #[cfg(feature = "mcxa5xx")] impl_instance!(2, 3, 4, 5); /// MOSI pin trait. pub trait MosiPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// MISO pin trait. pub trait MisoPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// SCK pin trait. pub trait SckPin: GpioPin + sealed::Sealed + PeripheralType { fn mux(&self); } /// Driver mode. #[allow(private_bounds)] pub trait Mode: sealed::Sealed {} /// Async driver mode. #[allow(private_bounds)] pub trait AsyncMode: sealed::Sealed + Mode {} /// Blocking mode. pub struct Blocking; impl sealed::Sealed for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl sealed::Sealed for Async {} impl Mode for Async {} impl AsyncMode for Async {} /// DMA mode. pub struct Dma<'d> { tx_dma: DmaChannel<'d>, rx_dma: DmaChannel<'d>, rx_request: DmaRequest, tx_request: DmaRequest, } impl sealed::Sealed for Dma<'_> {} impl Mode for Dma<'_> {} impl AsyncMode for Dma<'_> {} macro_rules! impl_pin { ($pin:ident, $peri:ident, $fn:ident, $trait:ident) => { impl sealed::Sealed for crate::peripherals::$pin {} impl $trait for crate::peripherals::$pin { fn mux(&self) { self.set_pull(crate::gpio::Pull::Disabled); self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); self.set_function(crate::pac::port::vals::Mux::$fn); self.set_enable_input_buffer(true); } } }; } #[cfg(feature = "swd-as-gpio")] impl_pin!(P0_1, LPSPI0, MUX3, MisoPin); #[cfg(feature = "swd-swo-as-gpio")] impl_pin!(P0_2, LPSPI0, MUX3, SckPin); #[cfg(feature = "jtag-extras-as-gpio")] impl_pin!(P0_3, LPSPI0, MUX3, MosiPin); impl_pin!(P1_0, LPSPI0, MUX2, MosiPin); impl_pin!(P1_1, LPSPI0, MUX2, SckPin); impl_pin!(P1_2, LPSPI0, MUX2, MisoPin); impl_pin!(P2_12, LPSPI1, MUX2, SckPin); impl_pin!(P2_13, LPSPI1, MUX2, MosiPin); impl_pin!(P2_15, LPSPI1, MUX2, MisoPin); impl_pin!(P2_16, LPSPI1, MUX2, MisoPin); impl_pin!(P3_8, LPSPI1, MUX2, MosiPin); impl_pin!(P3_9, LPSPI1, MUX2, MisoPin); impl_pin!(P3_10, LPSPI1, MUX2, SckPin); #[cfg(feature = "mcxa5xx")] mod mcxa5xx { use super::*; impl_pin!(P0_20, LPSPI4, MUX8, MisoPin); impl_pin!(P0_21, LPSPI4, MUX8, SckPin); impl_pin!(P0_22, LPSPI4, MUX8, MosiPin); impl_pin!(P1_12, LPSPI5, MUX5, MisoPin); impl_pin!(P1_13, LPSPI5, MUX5, SckPin); impl_pin!(P1_14, LPSPI5, MUX8, MosiPin); impl_pin!(P2_0, LPSPI2, MUX8, MisoPin); impl_pin!(P2_1, LPSPI2, MUX8, SckPin); impl_pin!(P2_2, LPSPI2, MUX8, MosiPin); impl_pin!(P3_4, LPSPI4, MUX3, MosiPin); impl_pin!(P3_3, LPSPI4, MUX3, SckPin); impl_pin!(P3_2, LPSPI4, MUX3, MisoPin); impl_pin!(P4_3, LPSPI2, MUX8, SckPin); impl_pin!(P4_4, LPSPI2, MUX8, MisoPin); impl_pin!(P4_5, LPSPI2, MUX8, MosiPin); impl_pin!(P4_8, LPSPI5, MUX8, MosiPin); impl_pin!(P4_9, LPSPI5, MUX8, MisoPin); impl_pin!(P4_10, LPSPI5, MUX8, SckPin); } ================================================ FILE: embassy-mcxa/src/trng.rs ================================================ //! True Random Number Generator use core::marker::PhantomData; use core::sync::atomic::{AtomicU32, Ordering}; use embassy_hal_internal::{Peri, PeripheralType}; use maitake_sync::WaitCell; use paste::paste; use crate::clocks::periph_helpers::NoConfig; use crate::clocks::{Gate, enable_and_reset}; use crate::interrupt::typelevel; use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac; use crate::pac::trng::regs::IntStatus; use crate::pac::trng::vals::{IntStatusEntVal, TrngEntCtl}; const BLOCK_SIZE: usize = 8; #[allow(private_bounds)] pub trait Mode: sealed::SealedMode {} mod sealed { pub trait SealedMode {} } /// Blocking driver mode. pub struct Blocking; impl sealed::SealedMode for Blocking {} impl Mode for Blocking {} /// Async driver mode. pub struct Async; impl sealed::SealedMode for Async {} impl Mode for Async {} /// TRNG Driver pub struct Trng<'d, M: Mode> { info: &'static Info, _phantom: PhantomData<&'d mut M>, } impl<'d, M: Mode> Trng<'d, M> { fn new_inner(_peri: Peri<'d, T>, config: Config) -> Self { // No clock: No WakeGuard! _ = unsafe { enable_and_reset::(&NoConfig) }; let mut inst = Self { info: T::info(), _phantom: PhantomData, }; inst.configure(config); inst } fn configure(&mut self, config: Config) { self.info.regs().mctl().modify(|w| { w.set_rst_def(true); w.set_prgm(true); w.set_err(true) }); self.info.regs().scml().write(|w| { w.set_mono_max(config.monobit_limit_max); w.set_mono_rng(config.monobit_limit_range); }); self.info.regs().scr1l().write(|w| { w.set_run1_max(config.run_length1_limit_max); w.set_run1_rng(config.run_length1_limit_range); }); self.info.regs().scr2l().write(|w| { w.set_run2_max(config.run_length2_limit_max); w.set_run2_rng(config.run_length2_limit_range); }); self.info.regs().scr3l().write(|w| { w.set_run3_max(config.run_length3_limit_max); w.set_run3_rng(config.run_length3_limit_range); }); self.info .regs() .frqmax() .write(|w| w.set_frq_max(config.freq_counter_max)); self.info .regs() .frqmin() .write(|w| w.set_frq_min(config.freq_counter_min)); self.info.regs().scmisc().write(|w| { w.set_lrun_max(config.long_run_limit_max); w.set_rty_ct(config.retry_count); }); self.info.regs().sdctl().write(|w| { w.set_samp_size(config.sample_size); w.set_ent_dly(config.entropy_delay); }); self.info .regs() .osc2_ctl() .modify(|w| w.set_trng_ent_ctl(config.osc_mode.into())); self.info.regs().mctl().modify(|w| w.set_prgm(false)); let _ = self.info.regs().ent(7).read(); self.start(); } fn start(&mut self) { #[cfg(feature = "mcxa2xx")] self.info.regs().mctl().modify(|w| w.set_trng_acc(true)); } fn stop(&mut self) { #[cfg(feature = "mcxa2xx")] self.info.regs().mctl().modify(|w| w.set_trng_acc(false)); } fn blocking_wait_for_generation(&mut self) { while !self.info.regs().mctl().read().ent_val() { if self.info.regs().mctl().read().err() { self.info.regs().mctl().modify(|w| w.set_err(true)); } } } fn fill_chunk(&mut self, chunk: &mut [u8]) { let mut entropy = [0u32; 8]; for (i, item) in entropy.iter_mut().enumerate() { *item = self.info.regs().ent(i).read().ent(); } let entropy: [u8; 32] = unsafe { core::mem::transmute(entropy) }; chunk.copy_from_slice(&entropy[..chunk.len()]); } // Blocking API /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, buf: &mut [u8]) { if buf.is_empty() { return; // nothing to fill } for chunk in buf.chunks_mut(32) { self.blocking_wait_for_generation(); self.fill_chunk(chunk); } } /// Return a random u32, blocking version. pub fn blocking_next_u32(&mut self) -> u32 { self.blocking_wait_for_generation(); // New random bytes are generated only after reading ENT7 self.info.regs().ent(7).read().ent() } /// Return a random u64, blocking version. pub fn blocking_next_u64(&mut self) -> u64 { self.blocking_wait_for_generation(); let mut result = u64::from(self.info.regs().ent(6).read().ent()) << 32; // New random bytes are generated only after reading ENT7 result |= u64::from(self.info.regs().ent(7).read().ent()); result } /// Return the full block of `[u32; 8]` generated by the hardware, /// blocking version. pub fn blocking_next_block(&mut self, block: &mut [u32; BLOCK_SIZE]) { self.blocking_wait_for_generation(); for (reg, result) in (0..8).map(|i| self.info.regs().ent(i)).zip(block.iter_mut()) { *result = reg.read().ent(); } } } impl<'d> Trng<'d, Blocking> { /// Instantiates a new TRNG peripheral driver with 128 samples of entropy. pub fn new_blocking_128(_peri: Peri<'d, T>) -> Self { Self::new_inner( _peri, Config { sample_size: 128, retry_count: 1, long_run_limit_max: 29, monobit_limit_max: 94, monobit_limit_range: 61, run_length1_limit_max: 39, run_length1_limit_range: 39, run_length2_limit_max: 24, run_length2_limit_range: 25, run_length3_limit_max: 17, run_length3_limit_range: 18, ..Default::default() }, ) } /// Instantiates a new TRNG peripheral driver with 256 samples of entropy. pub fn new_blocking_256(_peri: Peri<'d, T>) -> Self { Self::new_inner( _peri, Config { sample_size: 256, retry_count: 1, long_run_limit_max: 31, monobit_limit_max: 171, monobit_limit_range: 86, run_length1_limit_max: 63, run_length1_limit_range: 56, run_length2_limit_max: 38, run_length2_limit_range: 38, run_length3_limit_max: 25, run_length3_limit_range: 26, ..Default::default() }, ) } /// Instantiates a new TRNG peripheral driver with 512 samples of entropy. pub fn new_blocking_512(_peri: Peri<'d, T>) -> Self { Self::new_inner(_peri, Default::default()) } /// Instantiates a new TRNG peripheral driver. /// /// NOTE: this constructor makes no attempt at validating the /// parameters. If you get this wrong, the security guarantees of /// the TRNG with regards to entropy may be violated pub fn new_blocking_with_custom_config(_peri: Peri<'d, T>, config: Config) -> Self { Self::new_inner(_peri, config) } } impl<'d> Trng<'d, Async> { /// Instantiates a new TRNG peripheral driver with 128 samples of entropy. pub fn new_128( _peri: Peri<'d, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, ) -> Self { let inst = Self::new_inner( _peri, Config { sample_size: 128, retry_count: 1, long_run_limit_max: 29, monobit_limit_max: 94, monobit_limit_range: 61, run_length1_limit_max: 39, run_length1_limit_range: 39, run_length2_limit_max: 24, run_length2_limit_range: 25, run_length3_limit_max: 17, run_length3_limit_range: 18, ..Default::default() }, ); T::Interrupt::unpend(); INT_STAT.store(0, Ordering::Release); unsafe { T::Interrupt::enable(); } inst } /// Instantiates a new TRNG peripheral driver with 256 samples of entropy. pub fn new_256( _peri: Peri<'d, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, ) -> Self { let inst = Self::new_inner( _peri, Config { sample_size: 256, retry_count: 1, long_run_limit_max: 31, monobit_limit_max: 171, monobit_limit_range: 86, run_length1_limit_max: 63, run_length1_limit_range: 56, run_length2_limit_max: 38, run_length2_limit_range: 38, run_length3_limit_max: 25, run_length3_limit_range: 26, ..Default::default() }, ); T::Interrupt::unpend(); INT_STAT.store(0, Ordering::Release); unsafe { T::Interrupt::enable(); } inst } /// Instantiates a new TRNG peripheral driver with 512 samples of entropy. pub fn new_512( _peri: Peri<'d, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, ) -> Self { let inst = Self::new_inner(_peri, Default::default()); T::Interrupt::unpend(); INT_STAT.store(0, Ordering::Release); unsafe { T::Interrupt::enable(); } inst } /// Instantiates a new TRNG peripheral driver. /// /// NOTE: this constructor makes no attempt at validating the /// parameters. If you get this wrong, the security guarantees of /// the TRNG with regards to entropy may be violated pub fn new_with_custom_config( _peri: Peri<'d, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Self { let inst = Self::new_inner(_peri, config); T::Interrupt::unpend(); INT_STAT.store(0, Ordering::Release); unsafe { T::Interrupt::enable(); } inst } fn enable_ints(&mut self) { self.info.regs().int_mask().write(|w| { w.set_hw_err(true); w.set_ent_val(true); w.set_frq_ct_fail(true); w.set_intg_flt(true); }); } async fn wait_for_generation(&mut self) -> Result<(), Error> { self.info .wait_cell() .wait_for_value(|| { self.enable_ints(); let status = INT_STAT.swap(0, Ordering::AcqRel); if status == 0 { return None; } let status = IntStatus(status); if status.ent_val() == IntStatusEntVal::ENT_VAL_VALID { Some(Ok(())) } else if status.frq_ct_fail() { Some(Err(Error::FrequencyCountFail)) } else if status.hw_err() { Some(Err(Error::HardwareFail)) } else if status.intg_flt() { Some(Err(Error::IntegrityError)) } else { Some(Err(Error::ErrorStatus)) } }) .await .map_err(|_| Error::ErrorStatus) .flatten() } // Async API /// Fill the buffer with random bytes, async version. pub async fn async_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), Error> { if buf.is_empty() { return Ok(()); // nothing to fill } for chunk in buf.chunks_mut(32) { self.wait_for_generation().await?; self.fill_chunk(chunk); } Ok(()) } /// Return a random u32, async version. pub async fn async_next_u32(&mut self) -> Result { self.wait_for_generation().await?; // New random bytes are generated only after reading ENT7 Ok(self.info.regs().ent(7).read().ent()) } /// Return a random u64, async version. pub async fn async_next_u64(&mut self) -> Result { self.wait_for_generation().await?; let mut result = u64::from(self.info.regs().ent(6).read().ent()) << 32; // New random bytes are generated only after reading ENT7 result |= u64::from(self.info.regs().ent(7).read().ent()); Ok(result) } /// Return the full block of `[u32; 8]` generated by the hardware, /// async version. pub async fn async_next_block(&mut self, block: &mut [u32; BLOCK_SIZE]) -> Result<(), Error> { self.wait_for_generation().await?; for (reg, result) in (0..8).map(|i| self.info.regs().ent(i)).zip(block.iter_mut()) { *result = reg.read().ent(); } Ok(()) } } impl Drop for Trng<'_, M> { fn drop(&mut self) { // wait until allowed to stop while !self.info.regs().mctl().read().tstop_ok() {} // stop self.stop(); // reset the TRNG self.info.regs().mctl().write(|w| w.set_rst_def(true)); } } /// Trng errors #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { /// Integrity error. IntegrityError, /// Frequency counter fail FrequencyCountFail, /// Error status ErrorStatus, /// Buffer argument is invalid InvalidBuffer, /// Hardware fail HardwareFail, } static INT_STAT: AtomicU32 = AtomicU32::new(0); /// TRNG interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl Handler for InterruptHandler { unsafe fn on_interrupt() { T::PERF_INT_INCR(); let int_status = T::info().regs().int_status().read().0; INT_STAT.fetch_or(int_status, Ordering::AcqRel); if int_status != 0 { T::info().regs().int_ctrl().write(|w| { w.set_hw_err(false); w.set_ent_val(false); w.set_frq_ct_fail(false); w.set_intg_flt(false); }); T::PERF_INT_WAKE_INCR(); T::info().wait_cell().wake(); } } } /// True random number generator configuration parameters. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct Config { /// Total number of Entropy samples that will be taken during /// Entropy generation. pub sample_size: u16, /// Length (in system clocks) of each Entropy sample taken. pub entropy_delay: u16, /// Frequency Counter Maximum Limit pub freq_counter_max: u32, /// Frequency Counter Minimum Limit pub freq_counter_min: u32, /// Statistical check monobit max limit pub monobit_limit_max: u16, /// Statistical check monobit range pub monobit_limit_range: u16, /// Statistical check run length 1 limit max pub run_length1_limit_max: u16, /// Statistical check run length 1 limit range pub run_length1_limit_range: u16, /// Statistical check run length 2 limit max pub run_length2_limit_max: u16, /// Statistical check run length 2 limit range pub run_length2_limit_range: u16, /// Statistical check run length 3 limit max pub run_length3_limit_max: u16, /// Statistical check run length 3 limit range pub run_length3_limit_range: u16, /// Retry count pub retry_count: u8, /// Long run limit max pub long_run_limit_max: u8, /// Sparse bit limit pub sparse_bit_limit: u16, /// Oscillator mode pub osc_mode: OscMode, } impl Default for Config { fn default() -> Self { Self { sample_size: 512, entropy_delay: 32_000, freq_counter_max: 75_000, freq_counter_min: 30_000, monobit_limit_max: 317, monobit_limit_range: 122, run_length1_limit_max: 107, run_length1_limit_range: 80, run_length2_limit_max: 62, run_length2_limit_range: 55, run_length3_limit_max: 39, run_length3_limit_range: 39, retry_count: 1, long_run_limit_max: 32, sparse_bit_limit: 0, osc_mode: OscMode::DualOscs, } } } /// Sample size. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum SampleSize { /// 128 bits _128, /// 256 bits _256, /// 512 bits _512, } /// Oscillator mode. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum OscMode { /// Single oscillator using OSC1. SingleOsc1, /// Dual oscillator. DualOscs, /// Single oscillator using OSC2. SingleOsc2, } impl From for TrngEntCtl { fn from(value: OscMode) -> Self { match value { OscMode::SingleOsc1 => Self::TRNG_ENT_CTL_SINGLE_OSC1, OscMode::DualOscs => Self::TRNG_ENT_CTL_DUAL_OSCS, OscMode::SingleOsc2 => Self::TRNG_ENT_CTL_SINGLE_OSC2, } } } impl<'d, M: Mode> rand_core_06::RngCore for Trng<'d, M> { 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<(), rand_core_06::Error> { self.blocking_fill_bytes(dest); Ok(()) } } impl<'d, M: Mode> rand_core_06::CryptoRng for Trng<'d, M> {} impl<'d, M: Mode> rand_core_09::RngCore for Trng<'d, M> { 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); } } impl<'d, M: Mode> rand_core_09::CryptoRng for Trng<'d, M> {} impl<'d, M: Mode> rand_core_06::block::BlockRngCore for Trng<'d, M> { type Item = u32; type Results = [Self::Item; BLOCK_SIZE]; fn generate(&mut self, results: &mut Self::Results) { self.blocking_next_block(results); } } impl<'d, M: Mode> rand_core_09::block::BlockRngCore for Trng<'d, M> { type Item = u32; type Results = [Self::Item; BLOCK_SIZE]; fn generate(&mut self, results: &mut Self::Results) { self.blocking_next_block(results); } } impl<'d, M: Mode> rand_core_09::block::CryptoBlockRng for Trng<'d, M> {} trait SealedInstance: Gate { fn info() -> &'static Info; const PERF_INT_INCR: fn(); const PERF_INT_WAKE_INCR: fn(); } /// CRC Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this TRNG instance. type Interrupt: typelevel::Interrupt; } struct Info { regs: pac::trng::Trng, wait_cell: WaitCell, } impl Info { #[inline(always)] fn regs(&self) -> pac::trng::Trng { self.regs } #[inline(always)] fn wait_cell(&self) -> &WaitCell { &self.wait_cell } } unsafe impl Sync for Info {} macro_rules! impl_instance { ($($n:literal),*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], wait_cell: WaitCell::new(), }; &INFO } const PERF_INT_INCR: fn() = crate::perf_counters::[]; const PERF_INT_WAKE_INCR: fn() = crate::perf_counters::[]; } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0); ================================================ FILE: embassy-mcxa/src/wwdt.rs ================================================ //! Windowed Watchdog Timer (WWDT) driver for MCXA microcontrollers. //! //! The WWDT is a hardware timer that can reset the system or generate an interrupt if the software fails to //! periodically "feed" the watchdog within a specified time window. This helps detect //! and recover from software failures or system hangs. //! //! The FRO12M provides a 1 MHz clock (clk_1m) used as WWDT0 independant clock source. This clock is / 4 by an internal fixed divider. use core::marker::PhantomData; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_time::Duration; use paste::paste; use crate::clocks::periph_helpers::Clk1MConfig; use crate::clocks::{ClockError, Gate, WakeGuard, enable_and_reset}; use crate::interrupt::typelevel; use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac; use crate::pac::wwdt::vals::{Wden, Wdprotect, Wdreset}; /// WWDT0 Error types #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// Clock configuration error. ClockSetup(ClockError), TimeoutTooSmall, TimeoutTooLarge, WarningTooLarge, } /// WWDT configuration #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Config { /// The timeout period after which the watchdog will trigger pub timeout: Duration, pub warning: Option, } impl Default for Config { fn default() -> Self { Self { timeout: Duration::from_secs(1), warning: None, } } } /// Watchdog peripheral pub struct Watchdog<'d> { info: &'static Info, _phantom: PhantomData<&'d mut ()>, _wg: Option, } impl<'d> Watchdog<'d> { /// Create a new WWDT instance. /// /// Configure the WWDT, enables the interrupt, set the timeout and or warning value. /// /// # Arguments /// /// * `_peri` - The WWDT peripheral instance /// * `_irq` - Interrupt binding for WWDT0 /// * `config - WWDT config with timeout and optional warning value pub fn new( _peri: Peri<'d, T>, _irq: impl crate::interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { let parts = unsafe { enable_and_reset::(&Clk1MConfig).map_err(Error::ClockSetup)? }; let watchdog = Self { info: T::info(), _phantom: PhantomData, _wg: parts.wake_guard, }; let frequency = parts.freq / 4; let timeout_cycles = (frequency as u64 * config.timeout.as_micros()) / 1_000_000; // Ensure the value fits in u32 and is within valid range // // Writing a value below FFh causes 00_00FFh to load into the // register. Therefore, the minimum timeout interval is TWDCLK // X 256 X 4. if timeout_cycles > 0xFFFFFF { return Err(Error::TimeoutTooLarge); } if timeout_cycles <= 0xFF { return Err(Error::TimeoutTooSmall); } watchdog.set_timeout_value(timeout_cycles as u32); // Windows value is set to max at reset for no effect. if let Some(warning_value) = config.warning { let warning_cycles = (frequency as u64 * warning_value.as_micros()) / 1_000_000; if warning_cycles > 0x3FF { return Err(Error::WarningTooLarge); } watchdog.set_warning_value(warning_cycles as u16); watchdog.enable_interrupt(); } else { watchdog.enable_reset(); } watchdog.lock_oscillator(); T::Interrupt::unpend(); // Safety: `_irq` ensures an Interrupt Handler exists. unsafe { T::Interrupt::enable(); } Ok(watchdog) } /// Start the watchdog timer with the specified timeout period. pub fn start(&mut self) { self.enable(); self.feed(); // Set the WDPROTECT bit to false after the Feed Sequence (0xAA, 0x55) self.set_flexible_mode(); } /// Feed the watchdog to prevent timeout. /// /// This must be called periodically before the timeout period expires to prevent /// the watchdog from triggering a reset or interrupt. pub fn feed(&self) { critical_section::with(|_cs| { self.info.regs().feed().write(|w| w.set_feed(0xAA)); self.info.regs().feed().write(|w| w.set_feed(0x55)); }); } /// Enable the watchdog timer. /// Function is blocking until the watchdog is actually started. fn enable(&self) { self.info.regs().mod_().modify(|w| w.set_wden(Wden::RUN)); while self.info.regs().tc().read().count() == 0xFF {} } /// Set the watchdog protection mode to flexible. fn set_flexible_mode(&self) { self.info.regs().mod_().modify(|w| w.set_wdprotect(Wdprotect::FLEXIBLE)); } /// Enable interrupt mode. fn enable_interrupt(&self) { self.info.regs().mod_().modify(|w| w.set_wdreset(Wdreset::INTERRUPT)); } /// Enable reset mode. fn enable_reset(&self) { self.info.regs().mod_().modify(|w| w.set_wdreset(Wdreset::RESET)); } /// Set the timeout value in clock cycles. /// /// # Arguments /// /// * `timeout` - Number of clock cycles before timeout. fn set_timeout_value(&self, timeout: u32) { self.info.regs().tc().write(|w| w.set_count(timeout)); } /// Set the warning interrupt value in clock cycles. /// /// # Arguments /// /// * `warning` - Number of clock cycles before warning interrupt. fn set_warning_value(&self, warning: u16) { self.info.regs().warnint().write(|w| w.set_warnint(warning)); } /// Lock the oscillator to prevent disabling or powering down the watchdog oscillator. fn lock_oscillator(&self) { self.info.regs().mod_().modify(|w| w.set_lock(true)); } } /// WWDT interrupt handler. /// /// This handler is called when the watchdog warning interrupt fires. /// When reset happens, the interrupt handler will never be reached. pub struct InterruptHandler { _phantom: PhantomData, } impl Handler for InterruptHandler { unsafe fn on_interrupt() { crate::perf_counters::incr_interrupt_wwdt(); if T::info().regs().mod_().read().wdtof() { #[cfg(feature = "defmt")] defmt::trace!("WWDT0: Timeout occurred"); T::info().regs().mod_().modify(|w| w.set_wdtof(true)); } if T::info().regs().mod_().read().wdint() { #[cfg(feature = "defmt")] defmt::trace!("T::INFO().REGS()0: Warning interrupt"); T::info().regs().mod_().modify(|w| w.set_wdint(true)); } } } trait SealedInstance: Gate { fn info() -> &'static Info; } /// WWDT Instance #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this WWDT instance. type Interrupt: typelevel::Interrupt; } struct Info { regs: pac::wwdt::Wwdt, } impl Info { #[inline(always)] fn regs(&self) -> pac::wwdt::Wwdt { self.regs } } unsafe impl Sync for Info {} macro_rules! impl_instance { ($($n:literal);*) => { $( paste!{ impl SealedInstance for crate::peripherals::[] { fn info() -> &'static Info { static INFO: Info = Info { regs: pac::[], }; &INFO } } impl Instance for crate::peripherals::[] { type Interrupt = crate::interrupt::typelevel::[]; } } )* }; } impl_instance!(0); #[cfg(feature = "mcxa5xx")] impl_instance!(1); ================================================ FILE: embassy-microchip/CHANGELOG.md ================================================ # 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 - ReleaseDate - Add UART driver. - First commit. ================================================ FILE: embassy-microchip/Cargo.toml ================================================ [package] name = "embassy-microchip" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for the MEC and CEC family of microcontrollers" keywords = ["embedded", "async", "microchip", "mec", "cec", "embedded-hal"] categories = ["embedded", "hardware-support", "no-std", "asynchronous"] repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-microchip" # TODO: Remove to publish initial version publish = false [package.metadata.embassy] build = [ {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1721n_b0_lj", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1721n_b0_sz", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1723n_b0_lj", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1723n_b0_sz", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1723n_f0_sz", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1723n_p0_9y", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1724n_b0_lj", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1724n_b0_sz", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1725n_b0_lj", "rt", "unstable-pac"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mec1727n_b0_sz", "rt", "unstable-pac"]}, ] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-microchip-v$VERSION/embassy-microchip/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-microchip/src/" features = ["mec1723n_b0_sz", "defmt", "unstable-pac"] flavors = [ { regex_feature = ".*", target = "thumbv7em-none-eabihf" } ] [package.metadata.docs.rs] features = ["mec1723n_b0_sz", "defmt", "unstable-pac"] rustdoc-args = ["--cfg", "docsrs"] [features] default = ["rt"] ## Cortex-M runtime (enabled by default) rt = ["mec17xx-pac/rt"] ## Enable defmt defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mec17xx-pac/defmt"] ## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) unstable-pac = [] # 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. #! ### Chip selection features ## MEC1721N_B0_LJ mec1721n_b0_lj = ["mec17xx-pac/mec1721n_b0_sz"] ## MEC1721N_B0_SZ mec1721n_b0_sz = ["mec17xx-pac/mec1721n_b0_sz"] ## MEC1723N_B0_LJ mec1723n_b0_lj = ["mec17xx-pac/mec1723n_b0_lj"] ## MEC1723N_B0_SZ mec1723n_b0_sz = ["mec17xx-pac/mec1723n_b0_sz"] ## MEC1723N_F0_SZ mec1723n_f0_sz = ["mec17xx-pac/mec1723n_f0_sz"] ## MEC1723N_P0_9Y mec1723n_p0_9y = ["mec17xx-pac/mec1723n_p0_9y"] ## MEC1724N_B0_LJ mec1724n_b0_lj = ["mec17xx-pac/mec1724n_b0_lj"] ## MEC1723N_B0_SZ mec1724n_b0_sz = ["mec17xx-pac/mec1724n_b0_sz"] ## MEC1725 mec1725n_b0_lj = ["mec17xx-pac/mec1725n_b0_lj"] ## MEC1727N_B0_SZ mec1727n_b0_sz = ["mec17xx-pac/mec1727n_b0_sz"] [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = { version = "0.7", features = ["device"] } critical-section = "1.2.0" defmt = { version = "1.0", optional = true } document-features = "0.2.11" embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-hal-internal = { version = "0.5.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", features = ["tick-hz-48_000_000"] } embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-utils" } 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-io = "0.7" embedded-io-async = { version = "0.7.0" } fixed = "1.29.0" log = { version = "0.4.17", optional = true } nb = "1.0.0" # PAC mec17xx-pac = { version = "0.1.2", default-features = false, features = ["rt"] } ================================================ FILE: embassy-microchip/README.md ================================================ # Embassy Microchip HAL HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy Microchip HAL targets the Microchip MEC and CEC Family of MCUs. 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 of interrupts, so that applications can focus on business logic. 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-microchip documentation](https://docs.embassy.dev/embassy-microchip). ================================================ FILE: embassy-microchip/src/fmt.rs ================================================ #![macro_use] #![allow(unused)] use core::fmt::{Debug, Display, LowerHex}; #[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 = "defmt")] ::defmt::trace!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! debug { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::debug!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! info { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::info!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! warn { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::warn!($s $(, $x)*); #[cfg(not(feature = "defmt"))] let _ = ($( & $x ),*); } }; } #[collapse_debuginfo(yes)] macro_rules! error { ($s:literal $(, $x:expr)* $(,)?) => { { #[cfg(feature = "defmt")] ::defmt::error!($s $(, $x)*); #[cfg(not(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 Debug for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } impl Display for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } impl LowerHex for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } #[cfg(feature = "defmt")] impl defmt::Format for Bytes<'_> { fn format(&self, fmt: defmt::Formatter) { defmt::write!(fmt, "{:02x}", self.0) } } ================================================ FILE: embassy-microchip/src/gpio.rs ================================================ //! GPIO driver. #![macro_use] use core::future::Future; use core::pin::Pin as FuturePin; use core::task::{Context, Poll}; use cortex_m::interrupt::InterruptNumber; use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; use crate::interrupt::InterruptExt; use crate::pac::common::{R, RW, Reg}; use crate::pac::gpio::regs::{Ctrl1, Ctrl2}; use crate::{interrupt, pac, peripherals}; // Each interrupt aggregator block holds a maximum of 31 GPIOs. Some combine a // considerably smaller number of pins, but we're still allocating 31 // `AtomicWaker`s for those. pub(crate) const PIN_COUNT: usize = 31; static GIRQ08_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; static GIRQ09_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; static GIRQ10_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; static GIRQ11_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; static GIRQ12_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; static GIRQ26_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; /// Represents a digital input or output level. #[derive(Debug, Eq, PartialEq, Clone, Copy)] #[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::High => true, Level::Low => false, } } } /// 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, /// Repeater mode. Pin is kept at previous voltage level when no /// active driver is present on the pin. Repeater, } impl From for crate::pac::Pull { fn from(val: Pull) -> Self { match val { Pull::None => Self::NONE, Pull::Up => Self::UP, Pull::Down => Self::DOWN, Pull::Repeater => Self::REPEATER, } } } /// Drive strenght of an output. #[derive(Debug, Eq, PartialEq)] pub enum Drive { /// 2mA for PIO-12, 4mA for PIO-24, Weakest, /// 4mA for PIO-12, 8mA for PIO-24, Weak, /// 8mA for PIO-12, 16mA for PIO-24, Strong, /// 12mA for PIO-12, 24mA for PIO-24, Strongest, } impl From for crate::pac::Strength { fn from(val: Drive) -> Self { match val { Drive::Weakest => Self::LOWEST, Drive::Weak => Self::LOW, Drive::Strong => Self::MEDIUM, Drive::Strongest => Self::FULL, } } } /// Slow rate of an output. #[derive(Debug, Eq, PartialEq)] pub enum SlewRate { /// Slow (half-frequency) slew rate. Slow, /// Fast slew rate. Fast, } impl From for crate::pac::SlewCtrl { fn from(val: SlewRate) -> Self { match val { SlewRate::Slow => Self::SLOW, SlewRate::Fast => Self::FAST, } } } /// GPIO input driber. pub struct Input<'d> { pin: Flex<'d>, } impl<'d> Input<'d> { /// Create a GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(); pin.set_pull(pull); Self { pin } } /// Get wheter 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 the 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; } } /// 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 unsafe fn init() { // GPIO interrupts must go through the Interrupt Aggregator. let blk_en = 1 << (8 + interrupt::GIRQ08.number()) | 1 << (8 + interrupt::GIRQ09.number()) | 1 << (8 + interrupt::GIRQ10.number()) | 1 << (8 + interrupt::GIRQ11.number()) | 1 << (8 + interrupt::GIRQ12.number()) | 1 << (8 + interrupt::GIRQ26.number()); crate::pac::ECIA.blk_en_set().write(|w| w.set_vtor_en_set(blk_en)); interrupt::GIRQ08.disable(); interrupt::GIRQ09.disable(); interrupt::GIRQ10.disable(); interrupt::GIRQ11.disable(); interrupt::GIRQ12.disable(); interrupt::GIRQ26.disable(); interrupt::GIRQ08.set_priority(interrupt::Priority::P7); interrupt::GIRQ09.set_priority(interrupt::Priority::P7); interrupt::GIRQ10.set_priority(interrupt::Priority::P7); interrupt::GIRQ11.set_priority(interrupt::Priority::P7); interrupt::GIRQ12.set_priority(interrupt::Priority::P7); interrupt::GIRQ26.set_priority(interrupt::Priority::P7); interrupt::GIRQ08.unpend(); interrupt::GIRQ09.unpend(); interrupt::GIRQ10.unpend(); interrupt::GIRQ11.unpend(); interrupt::GIRQ12.unpend(); interrupt::GIRQ26.unpend(); unsafe { interrupt::GIRQ08.enable(); interrupt::GIRQ09.enable(); interrupt::GIRQ10.enable(); interrupt::GIRQ11.enable(); interrupt::GIRQ12.enable(); interrupt::GIRQ26.enable(); } } #[cfg(feature = "rt")] #[interrupt] fn GIRQ08() { let regs = InterruptRegs { result: crate::pac::ECIA.result8(), clr: crate::pac::ECIA.en_clr8(), }; irq_handler(regs, &GIRQ08_WAKERS); } #[cfg(feature = "rt")] #[interrupt] fn GIRQ09() { let regs = InterruptRegs { result: crate::pac::ECIA.result9(), clr: crate::pac::ECIA.en_clr9(), }; irq_handler(regs, &GIRQ09_WAKERS); } #[cfg(feature = "rt")] #[interrupt] fn GIRQ10() { let regs = InterruptRegs { result: crate::pac::ECIA.result10(), clr: crate::pac::ECIA.en_clr10(), }; irq_handler(regs, &GIRQ10_WAKERS); } #[cfg(feature = "rt")] #[interrupt] fn GIRQ11() { let regs = InterruptRegs { result: crate::pac::ECIA.result11(), clr: crate::pac::ECIA.en_clr11(), }; irq_handler(regs, &GIRQ11_WAKERS); } #[cfg(feature = "rt")] #[interrupt] fn GIRQ12() { let regs = InterruptRegs { result: crate::pac::ECIA.result12(), clr: crate::pac::ECIA.en_clr12(), }; irq_handler(regs, &GIRQ12_WAKERS); } #[cfg(feature = "rt")] #[interrupt] fn GIRQ26() { let regs = InterruptRegs { result: crate::pac::ECIA.result26(), clr: crate::pac::ECIA.en_clr26(), }; irq_handler(regs, &GIRQ26_WAKERS); } #[cfg(feature = "rt")] #[inline] fn irq_handler(regs: InterruptRegs, wakers: &[AtomicWaker; PIN_COUNT]) { let result = regs.result.read(); for bit in 0..PIN_COUNT { if result & (1 << bit) != 0 { // mask event regs.clr.write_value(1 << bit); wakers[bit].wake(); } } } #[must_use = "futures do nothing unless you `.await` or poll them"] struct InputFuture<'d> { pin: Peri<'d, AnyPin>, trigger: InterruptTrigger, } impl<'d> InputFuture<'d> { fn new(pin: Peri<'d, AnyPin>, trigger: InterruptTrigger) -> Self { pin.enable_interrupts(trigger); Self { pin, trigger } } } impl<'d> Unpin for InputFuture<'d> {} impl<'d> Future for InputFuture<'d> { type Output = (); fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { let waker = self.pin.waker(); waker.register(cx.waker()); // If user requested a level change, then we must check the // current level of the pin. If it matches the requested // level, we're done. Just produce a `Poll::Ready`. If, // however, an edge change was requested, we must wait for the // actual interrupt. let level_change = match self.trigger { InterruptTrigger::LevelLow => !self.pin.regs().ctrl1.read().inp(), InterruptTrigger::LevelHigh => self.pin.regs().ctrl1.read().inp(), _ => false, }; // IRQ handler will mask the interrupt if the event has occurred. If the // bit for $this event is cleared, that means we triggered an interrupt // and an return control to the application. let set = self.pin.regs().set.read(); if level_change || (set & (1 << self.pin.irq_bit()) == 0) { Poll::Ready(()) } else { // unmask event so other events can be sampled self.pin.regs().set.write_value(1 << self.pin.irq_bit()); Poll::Pending } } } impl<'d> Drop for InputFuture<'d> { fn drop(&mut self) { self.pin.disable_interrupts(); } } /// 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: Peri<'d, impl Pin>, 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 outpt 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 outpt 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. 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: Peri<'d, impl Pin>, initial_output: Level) -> Self { let mut pin = Flex::new(pin); pin.set_as_output_open_drain(); pin.set_level(initial_output); Self { pin } } /// Set the pin's pull-up. #[inline] pub fn set_pullup(&mut self, enable: bool) { if enable { self.pin.set_pull(Pull::Up) } else { self.pin.set_pull(Pull::None) } } /// 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 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() } /// 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; } } /// 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: Peri<'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: Peri<'d, impl Pin>) -> Self { critical_section::with(|_| { pin.regs().ctrl1.modify(|w| { w.set_mux_ctrl(crate::pac::Function::GPIO); w.set_out_sel(crate::pac::Sel::PIN); }) }); let pin = pin.into(); pin.disable_interrupts(); Self { pin } } /// Set the pin's pull. #[inline] pub fn set_pull(&mut self, pull: Pull) { critical_section::with(|_| { self.pin.regs().ctrl1.modify(|w| w.set_pu_pd(pull.into())); }); } /// Set the pin's drive stength #[inline] pub fn set_drive_strength(&mut self, strength: Drive) { critical_section::with(|_| { self.pin.regs().ctrl2.modify(|w| w.set_driv_stren(strength.into())); }); } /// Set the pin's slew rate. #[inline] pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { critical_section::with(|_| { self.pin.regs().ctrl2.modify(|w| w.set_slew_ctrl(slew_rate.into())); }); } /// Put the pin into input mode. /// /// The pull setting is left unchanged. #[inline] pub fn set_as_input(&mut self) { critical_section::with(|_| { self.pin.regs().ctrl1.modify(|w| { w.set_dir(crate::pac::Dir::INPUT); w.set_inp_dis(false); }) }); } /// 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) { critical_section::with(|_| self.pin.regs().ctrl1.modify(|w| w.set_dir(crate::pac::Dir::OUTPUT))); } /// Put the pin into output open drain 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_open_drain(&mut self) { critical_section::with(|_| { self.pin.regs().ctrl1.modify(|w| { w.set_dir(crate::pac::Dir::OUTPUT); w.set_out_buff_type(crate::pac::BufferType::OPEN_DRAIN); }) }); } /// Set as output pin. #[inline] fn is_set_as_output(&self) -> bool { self.pin.regs().ctrl1.read().dir() == crate::pac::Dir::OUTPUT } /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { self.pin.regs().ctrl1.read().inp() } /// Get whether the pin input level is low. #[inline] pub fn is_low(&self) -> bool { !self.is_high() } /// 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) { critical_section::with(|_| { self.pin.regs().ctrl1.modify(|w| w.set_alt_data(true)); }); } /// Set the output as low. #[inline] pub fn set_low(&mut self) { critical_section::with(|_| { self.pin.regs().ctrl1.modify(|w| w.set_alt_data(false)); }); } /// 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.pin.regs().ctrl1.read().alt_data() } /// Is the output level low? #[inline] pub fn is_set_low(&self) -> bool { !self.is_set_high() } /// What level output is set to #[inline] pub fn get_output_level(&self) -> Level { self.is_set_high().into() } /// Toggle the output pin #[inline] pub fn toggle(&mut self) { critical_section::with(|_| { let val = self.pin.regs().ctrl1.read().alt_data(); self.pin.regs().ctrl1.modify(|w| w.set_alt_data(!val)); }); } /// 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; } } impl<'d> Drop for Flex<'d> { #[inline] fn drop(&mut self) { todo!() } } pub(crate) trait SealedPin: Sized { fn _pin(&self) -> u8; fn _port(&self) -> u8; fn regs(&self) -> Registers; fn irq_id(&self) -> usize; fn irq_bit(&self) -> usize; fn waker(&self) -> &AtomicWaker { match self.irq_id() { 0 => &GIRQ08_WAKERS[self.irq_bit()], 1 => &GIRQ09_WAKERS[self.irq_bit()], 2 => &GIRQ10_WAKERS[self.irq_bit()], 3 => &GIRQ11_WAKERS[self.irq_bit()], 4 => &GIRQ12_WAKERS[self.irq_bit()], 17 => &GIRQ26_WAKERS[self.irq_bit()], _ => unreachable!(), } } } #[derive(Clone, Copy)] struct InterruptRegs { result: Reg, clr: Reg, } #[derive(Clone, Copy)] pub(crate) struct Registers { pub(crate) ctrl1: Reg, pub(crate) ctrl2: Reg, pub(crate) src: Reg, pub(crate) set: Reg, pub(crate) clr: Reg, } /// 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: PeripheralType + Into + SealedPin + Sized + 'static { /// Returns the pin number within a port #[inline] fn pin(&self) -> u8 { self._pin() } #[inline] fn port(&self) -> u8 { self._port() } } /// Type-erased GPIO pin pub struct AnyPin { pin: u8, port: u8, regs: Registers, irq_id: usize, irq_bit: usize, } impl AnyPin { fn enable_interrupts(&self, trigger: InterruptTrigger) { critical_section::with(|_| { self.regs().clr.write_value(1 << self.irq_bit); self.regs().ctrl1.modify(|w| match trigger { InterruptTrigger::LevelLow => { w.set_edge_en(false); w.set_intr_det(0); } InterruptTrigger::LevelHigh => { w.set_edge_en(false); w.set_intr_det(1); } InterruptTrigger::EdgeLow => { w.set_edge_en(true); w.set_intr_det(6); } InterruptTrigger::EdgeHigh => { w.set_edge_en(true); w.set_intr_det(5); } InterruptTrigger::AnyEdge => { w.set_edge_en(true); w.set_intr_det(7); } }); self.regs().src.write_value(1 << self.irq_bit); self.regs().set.write_value(1 << self.irq_bit); }); } fn disable_interrupts(&self) { critical_section::with(|_| { self.regs().ctrl1.modify(|w| { w.set_edge_en(false); w.set_intr_det(4); }); self.regs().clr.write_value(1 << self.irq_bit); }); } } impl_peripheral!(AnyPin); impl Pin for AnyPin {} impl SealedPin for AnyPin { #[inline] fn _pin(&self) -> u8 { self.pin } #[inline] fn _port(&self) -> u8 { self.port } #[inline] fn regs(&self) -> Registers { self.regs } #[inline] fn irq_id(&self) -> usize { self.irq_id } #[inline] fn irq_bit(&self) -> usize { self.irq_bit } } macro_rules! impl_pin { ($name:ident, $port:expr, $pin:expr, $irq_id:expr, $irq_bit:expr, $src:ident, $set:ident, $result:ident, $clr:ident) => { impl Pin for peripherals::$name {} impl SealedPin for peripherals::$name { #[inline] fn _pin(&self) -> u8 { $pin } #[inline] fn _port(&self) -> u8 { $port } #[inline] fn irq_id(&self) -> usize { $irq_id } #[inline] fn irq_bit(&self) -> usize { $irq_bit } #[inline] fn regs(&self) -> Registers { let ptr = pac::GPIO.as_ptr(); let ctrl1 = unsafe { Reg::from_ptr(ptr.byte_add((($port / 10) << 8) + (($port % 10) << 5) + ($pin * 4)) as _) }; let ctrl2 = unsafe { Reg::from_ptr(ptr.byte_add(0x500 + (($port / 10) << 8) + (($port % 10) << 5) + ($pin * 4)) as _) }; Registers { ctrl1, ctrl2, src: crate::pac::ECIA.$src(), set: crate::pac::ECIA.$set(), clr: crate::pac::ECIA.$clr(), } } } impl From for AnyPin { fn from(val: peripherals::$name) -> Self { Self { pin: val._pin(), port: val._port(), regs: val.regs(), irq_bit: val.irq_bit(), irq_id: val.irq_id(), } } } }; } impl_pin!(GPIO0, 0, 0, 3, 0, src11, en_set11, result11, en_clr11); impl_pin!(GPIO1, 0, 1, 3, 1, src11, en_set11, result11, en_clr11); impl_pin!(GPIO2, 0, 2, 3, 2, src11, en_set11, result11, en_clr11); impl_pin!(GPIO3, 0, 3, 3, 3, src11, en_set11, result11, en_clr11); impl_pin!(GPIO4, 0, 4, 3, 4, src11, en_set11, result11, en_clr11); impl_pin!(GPIO5, 0, 5, 3, 5, src11, en_set11, result11, en_clr11); impl_pin!(GPIO6, 0, 6, 3, 6, src11, en_set11, result11, en_clr11); impl_pin!(GPIO7, 0, 7, 3, 7, src11, en_set11, result11, en_clr11); impl_pin!(GPIO10, 1, 0, 3, 8, src11, en_set11, result11, en_clr11); impl_pin!(GPIO11, 1, 1, 3, 9, src11, en_set11, result11, en_clr11); impl_pin!(GPIO12, 1, 2, 3, 10, src11, en_set11, result11, en_clr11); impl_pin!(GPIO13, 1, 3, 3, 11, src11, en_set11, result11, en_clr11); impl_pin!(GPIO14, 1, 4, 3, 12, src11, en_set11, result11, en_clr11); impl_pin!(GPIO15, 1, 5, 3, 13, src11, en_set11, result11, en_clr11); impl_pin!(GPIO16, 1, 6, 3, 14, src11, en_set11, result11, en_clr11); impl_pin!(GPIO17, 1, 7, 3, 15, src11, en_set11, result11, en_clr11); impl_pin!(GPIO20, 2, 0, 3, 16, src11, en_set11, result11, en_clr11); impl_pin!(GPIO21, 2, 1, 3, 17, src11, en_set11, result11, en_clr11); impl_pin!(GPIO22, 2, 2, 3, 18, src11, en_set11, result11, en_clr11); impl_pin!(GPIO23, 2, 3, 3, 19, src11, en_set11, result11, en_clr11); impl_pin!(GPIO24, 2, 4, 3, 20, src11, en_set11, result11, en_clr11); impl_pin!(GPIO25, 2, 5, 3, 21, src11, en_set11, result11, en_clr11); impl_pin!(GPIO26, 2, 6, 3, 22, src11, en_set11, result11, en_clr11); impl_pin!(GPIO27, 2, 7, 3, 23, src11, en_set11, result11, en_clr11); impl_pin!(GPIO30, 3, 0, 3, 24, src11, en_set11, result11, en_clr11); impl_pin!(GPIO31, 3, 1, 3, 25, src11, en_set11, result11, en_clr11); impl_pin!(GPIO32, 3, 2, 3, 26, src11, en_set11, result11, en_clr11); impl_pin!(GPIO33, 3, 3, 3, 27, src11, en_set11, result11, en_clr11); impl_pin!(GPIO34, 3, 4, 3, 28, src11, en_set11, result11, en_clr11); impl_pin!(GPIO35, 3, 5, 3, 29, src11, en_set11, result11, en_clr11); impl_pin!(GPIO36, 3, 6, 3, 30, src11, en_set11, result11, en_clr11); impl_pin!(GPIO40, 4, 0, 2, 0, src10, en_set10, result10, en_clr10); impl_pin!(GPIO41, 4, 1, 2, 1, src10, en_set10, result10, en_clr10); impl_pin!(GPIO42, 4, 2, 2, 2, src10, en_set10, result10, en_clr10); impl_pin!(GPIO43, 4, 3, 2, 3, src10, en_set10, result10, en_clr10); impl_pin!(GPIO44, 4, 4, 2, 4, src10, en_set10, result10, en_clr10); impl_pin!(GPIO45, 4, 5, 2, 5, src10, en_set10, result10, en_clr10); impl_pin!(GPIO46, 4, 6, 2, 6, src10, en_set10, result10, en_clr10); impl_pin!(GPIO47, 4, 7, 2, 7, src10, en_set10, result10, en_clr10); impl_pin!(GPIO50, 5, 0, 2, 8, src10, en_set10, result10, en_clr10); impl_pin!(GPIO51, 5, 1, 2, 9, src10, en_set10, result10, en_clr10); impl_pin!(GPIO52, 5, 2, 2, 10, src10, en_set10, result10, en_clr10); impl_pin!(GPIO53, 5, 3, 2, 11, src10, en_set10, result10, en_clr10); impl_pin!(GPIO54, 5, 4, 2, 12, src10, en_set10, result10, en_clr10); impl_pin!(GPIO55, 5, 5, 2, 13, src10, en_set10, result10, en_clr10); impl_pin!(GPIO56, 5, 6, 2, 14, src10, en_set10, result10, en_clr10); impl_pin!(GPIO57, 5, 7, 2, 15, src10, en_set10, result10, en_clr10); impl_pin!(GPIO60, 6, 0, 2, 16, src10, en_set10, result10, en_clr10); impl_pin!(GPIO61, 6, 1, 2, 17, src10, en_set10, result10, en_clr10); impl_pin!(GPIO62, 6, 2, 2, 18, src10, en_set10, result10, en_clr10); impl_pin!(GPIO63, 6, 3, 2, 19, src10, en_set10, result10, en_clr10); impl_pin!(GPIO64, 6, 4, 2, 20, src10, en_set10, result10, en_clr10); impl_pin!(GPIO65, 6, 5, 2, 21, src10, en_set10, result10, en_clr10); impl_pin!(GPIO66, 6, 6, 2, 22, src10, en_set10, result10, en_clr10); impl_pin!(GPIO67, 6, 7, 2, 23, src10, en_set10, result10, en_clr10); impl_pin!(GPIO70, 7, 0, 2, 24, src10, en_set10, result10, en_clr10); impl_pin!(GPIO71, 7, 1, 2, 25, src10, en_set10, result10, en_clr10); impl_pin!(GPIO72, 7, 2, 2, 26, src10, en_set10, result10, en_clr10); impl_pin!(GPIO73, 7, 3, 2, 27, src10, en_set10, result10, en_clr10); impl_pin!(GPIO74, 7, 4, 2, 28, src10, en_set10, result10, en_clr10); impl_pin!(GPIO75, 7, 5, 2, 29, src10, en_set10, result10, en_clr10); impl_pin!(GPIO76, 7, 6, 2, 30, src10, en_set10, result10, en_clr10); impl_pin!(GPIO100, 10, 0, 1, 0, src9, en_set9, result9, en_clr9); impl_pin!(GPIO101, 10, 1, 1, 1, src9, en_set9, result9, en_clr9); impl_pin!(GPIO102, 10, 2, 1, 2, src9, en_set9, result9, en_clr9); impl_pin!(GPIO103, 10, 3, 1, 3, src9, en_set9, result9, en_clr9); impl_pin!(GPIO104, 10, 4, 1, 4, src9, en_set9, result9, en_clr9); impl_pin!(GPIO105, 10, 5, 1, 5, src9, en_set9, result9, en_clr9); impl_pin!(GPIO106, 10, 6, 1, 6, src9, en_set9, result9, en_clr9); impl_pin!(GPIO107, 10, 7, 1, 7, src9, en_set9, result9, en_clr9); impl_pin!(GPIO112, 11, 2, 1, 10, src9, en_set9, result9, en_clr9); impl_pin!(GPIO113, 11, 3, 1, 11, src9, en_set9, result9, en_clr9); impl_pin!(GPIO114, 11, 4, 1, 12, src9, en_set9, result9, en_clr9); impl_pin!(GPIO115, 11, 5, 1, 13, src9, en_set9, result9, en_clr9); impl_pin!(GPIO116, 11, 6, 1, 14, src9, en_set9, result9, en_clr9); impl_pin!(GPIO117, 11, 7, 1, 15, src9, en_set9, result9, en_clr9); impl_pin!(GPIO120, 12, 0, 1, 16, src9, en_set9, result9, en_clr9); impl_pin!(GPIO121, 12, 1, 1, 17, src9, en_set9, result9, en_clr9); impl_pin!(GPIO122, 12, 2, 1, 18, src9, en_set9, result9, en_clr9); impl_pin!(GPIO123, 12, 3, 1, 19, src9, en_set9, result9, en_clr9); impl_pin!(GPIO124, 12, 4, 1, 20, src9, en_set9, result9, en_clr9); impl_pin!(GPIO125, 12, 5, 1, 21, src9, en_set9, result9, en_clr9); impl_pin!(GPIO126, 12, 6, 1, 22, src9, en_set9, result9, en_clr9); impl_pin!(GPIO127, 12, 7, 1, 23, src9, en_set9, result9, en_clr9); impl_pin!(GPIO130, 13, 0, 1, 24, src9, en_set9, result9, en_clr9); impl_pin!(GPIO131, 13, 1, 1, 25, src9, en_set9, result9, en_clr9); impl_pin!(GPIO132, 13, 2, 1, 26, src9, en_set9, result9, en_clr9); impl_pin!(GPIO133, 13, 3, 1, 27, src9, en_set9, result9, en_clr9); impl_pin!(GPIO134, 13, 4, 1, 28, src9, en_set9, result9, en_clr9); impl_pin!(GPIO135, 13, 5, 1, 29, src9, en_set9, result9, en_clr9); impl_pin!(GPIO140, 14, 0, 0, 0, src8, en_set8, result8, en_clr8); impl_pin!(GPIO141, 14, 1, 0, 1, src8, en_set8, result8, en_clr8); impl_pin!(GPIO142, 14, 2, 0, 2, src8, en_set8, result8, en_clr8); impl_pin!(GPIO143, 14, 3, 0, 3, src8, en_set8, result8, en_clr8); impl_pin!(GPIO144, 14, 4, 0, 4, src8, en_set8, result8, en_clr8); impl_pin!(GPIO145, 14, 5, 0, 5, src8, en_set8, result8, en_clr8); impl_pin!(GPIO146, 14, 6, 0, 6, src8, en_set8, result8, en_clr8); impl_pin!(GPIO147, 14, 7, 0, 7, src8, en_set8, result8, en_clr8); impl_pin!(GPIO150, 15, 0, 0, 8, src8, en_set8, result8, en_clr8); impl_pin!(GPIO151, 15, 1, 0, 9, src8, en_set8, result8, en_clr8); impl_pin!(GPIO152, 15, 2, 0, 10, src8, en_set8, result8, en_clr8); impl_pin!(GPIO153, 15, 3, 0, 11, src8, en_set8, result8, en_clr8); impl_pin!(GPIO154, 15, 4, 0, 12, src8, en_set8, result8, en_clr8); impl_pin!(GPIO155, 15, 5, 0, 13, src8, en_set8, result8, en_clr8); impl_pin!(GPIO156, 15, 6, 0, 14, src8, en_set8, result8, en_clr8); impl_pin!(GPIO157, 15, 7, 0, 15, src8, en_set8, result8, en_clr8); impl_pin!(GPIO160, 16, 0, 0, 16, src8, en_set8, result8, en_clr8); impl_pin!(GPIO161, 16, 1, 0, 17, src8, en_set8, result8, en_clr8); impl_pin!(GPIO162, 16, 2, 0, 18, src8, en_set8, result8, en_clr8); impl_pin!(GPIO165, 16, 5, 0, 21, src8, en_set8, result8, en_clr8); impl_pin!(GPIO166, 16, 6, 0, 22, src8, en_set8, result8, en_clr8); impl_pin!(GPIO170, 17, 0, 0, 24, src8, en_set8, result8, en_clr8); impl_pin!(GPIO171, 17, 1, 0, 25, src8, en_set8, result8, en_clr8); impl_pin!(GPIO172, 17, 2, 0, 26, src8, en_set8, result8, en_clr8); impl_pin!(GPIO173, 17, 3, 0, 27, src8, en_set8, result8, en_clr8); impl_pin!(GPIO174, 17, 4, 0, 28, src8, en_set8, result8, en_clr8); impl_pin!(GPIO175, 17, 5, 0, 29, src8, en_set8, result8, en_clr8); impl_pin!(GPIO200, 20, 0, 4, 0, src12, en_set12, result12, en_clr12); impl_pin!(GPIO201, 20, 1, 4, 1, src12, en_set12, result12, en_clr12); impl_pin!(GPIO202, 20, 2, 4, 2, src12, en_set12, result12, en_clr12); impl_pin!(GPIO203, 20, 3, 4, 3, src12, en_set12, result12, en_clr12); impl_pin!(GPIO204, 20, 4, 4, 4, src12, en_set12, result12, en_clr12); impl_pin!(GPIO205, 20, 5, 4, 5, src12, en_set12, result12, en_clr12); impl_pin!(GPIO206, 20, 6, 4, 6, src12, en_set12, result12, en_clr12); impl_pin!(GPIO207, 20, 7, 4, 7, src12, en_set12, result12, en_clr12); impl_pin!(GPIO210, 21, 0, 4, 8, src12, en_set12, result12, en_clr12); impl_pin!(GPIO211, 21, 1, 4, 9, src12, en_set12, result12, en_clr12); impl_pin!(GPIO212, 21, 2, 4, 10, src12, en_set12, result12, en_clr12); impl_pin!(GPIO213, 21, 3, 4, 11, src12, en_set12, result12, en_clr12); impl_pin!(GPIO214, 21, 4, 4, 12, src12, en_set12, result12, en_clr12); impl_pin!(GPIO215, 21, 5, 4, 13, src12, en_set12, result12, en_clr12); impl_pin!(GPIO216, 21, 6, 4, 14, src12, en_set12, result12, en_clr12); impl_pin!(GPIO217, 21, 7, 4, 15, src12, en_set12, result12, en_clr12); impl_pin!(GPIO221, 22, 1, 4, 17, src12, en_set12, result12, en_clr12); impl_pin!(GPIO222, 22, 2, 4, 18, src12, en_set12, result12, en_clr12); impl_pin!(GPIO223, 22, 3, 4, 19, src12, en_set12, result12, en_clr12); impl_pin!(GPIO224, 22, 4, 4, 20, src12, en_set12, result12, en_clr12); impl_pin!(GPIO225, 22, 5, 4, 21, src12, en_set12, result12, en_clr12); impl_pin!(GPIO226, 22, 6, 4, 22, src12, en_set12, result12, en_clr12); impl_pin!(GPIO227, 22, 7, 4, 23, src12, en_set12, result12, en_clr12); impl_pin!(GPIO230, 23, 0, 4, 23, src11, en_set11, result11, en_clr11); impl_pin!(GPIO231, 23, 0, 4, 23, src11, en_set11, result11, en_clr11); impl_pin!(GPIO240, 24, 0, 17, 0, src26, en_set26, result26, en_clr26); impl_pin!(GPIO241, 24, 1, 17, 1, src26, en_set26, result26, en_clr26); impl_pin!(GPIO242, 24, 2, 17, 2, src26, en_set26, result26, en_clr26); impl_pin!(GPIO243, 24, 3, 17, 3, src26, en_set26, result26, en_clr26); impl_pin!(GPIO244, 24, 4, 17, 4, src26, en_set26, result26, en_clr26); impl_pin!(GPIO245, 24, 5, 17, 5, src26, en_set26, result26, en_clr26); impl_pin!(GPIO246, 24, 6, 17, 6, src26, en_set26, result26, en_clr26); impl_pin!(GPIO247, 24, 7, 17, 7, src26, en_set26, result26, en_clr26); impl_pin!(GPIO254, 25, 4, 17, 12, src26, en_set26, result26, en_clr26); impl_pin!(GPIO255, 25, 5, 17, 13, src26, en_set26, result26, en_clr26); ================================================ FILE: embassy-microchip/src/i2c.rs ================================================ //! I2C driver. use core::future; use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use pac::smb0; use crate::gpio::{AnyPin, SealedPin}; use crate::interrupt::typelevel::{Binding, Interrupt}; use crate::{interrupt, pac, peripherals}; /// 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. NoAcknowledge, /// Lost arbitration ArbitrationLoss, /// Other reason Other, } /// I2C error bus timeout reason #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BusTimeoutReason { /// Clock high, data high timeout ClockHighDataHigh, /// Clock high, data low timeout ClockHighDataLow, /// Slave cumulative timeout SlaveCumulativeTimeout, /// Master cumulative timeout MasterCumulativeTimeout, /// Device timeout DeviceTimeout, } /// I2C error #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// I2C abort with error Abort(AbortReason), /// Bus timeout BusTimeout(BusTimeoutReason), /// User passed in a read buffer with 0 length InvalidReadBufferLength, /// User passed in a write buffer with 0 length InvalidWriteBufferLength, /// Target I2C address is out of range AddressOutOfRange(u8), /// Target I2C address is reserved AddressReserved(u8), } /// I2C config error #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ConfigError { /// Max I2C frequency is 1MHz FrequencyTooHigh, /// The baud rate clock is too slow to support the requested /// frequency ClockTooSlow, /// The baud rate clock is too fast to support the requested /// frequency ClockTooFast, } /// I2C bus speeds #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BusSpeed { /// 100kHz Standard, /// 400kHz, Fast, /// 1MHz, FastPlus, } /// I2C config #[non_exhaustive] #[derive(Copy, Clone)] pub struct Config { /// Bus speed. pub speed: BusSpeed, /// Own address 1 in 7-bit format. /// /// The internal master should not attempt transactions to a slave /// with the same address as `addr1`. This represents an illegal /// operation. /// /// Passing `None` results in the value 0 being written to the Own /// Address register and General Call Match to be disabled. pub addr1: Option, /// Own address 2 in 7-bit format. /// /// The internal master should not attempt transactions to a slave /// with the same address as `addr2`. This represents an illegal /// operation. /// /// Passing `None` results in the value 0 being written to the Own /// Address register and General Call Match to be disabled. pub addr2: Option, } impl Default for Config { fn default() -> Self { Self { speed: BusSpeed::Standard, addr1: None, addr2: None, } } } macro_rules! write_register_with_delay { ($reg_path:expr, |$w:ident| $body:block) => {{ $reg_path.write(|$w| $body); // TODO - empirically determined delay cortex_m::asm::delay(20_000); }}; } /// I2C driver. pub struct I2c<'d, T: Instance, M: Mode> { _peri: Peri<'d, T>, _sda: Peri<'d, AnyPin>, _scl: Peri<'d, AnyPin>, config: Config, port: u8, phantom: PhantomData, } impl<'d, T: Instance> I2c<'d, T, Blocking> { /// Create a new driver instance in blocking mode. pub fn new_blocking( _peri: Peri<'d, T>, _scl: Peri<'d, SCL>, _sda: Peri<'d, SDA>, config: Config, ) -> Self where (T, SCL, SDA): ValidI2cConfig, { _scl.setup(); _sda.setup(); let port = <(T, SCL, SDA) as SealedValidI2cConfig>::port(); Self::new_inner(_peri, _scl.into(), _sda.into(), config, port) } } impl<'d, T: Instance> I2c<'d, T, Async> { /// Create a new driver instance in async mode. pub fn new_async( _peri: Peri<'d, T>, _scl: Peri<'d, SCL>, _sda: Peri<'d, SDA>, _irq: impl Binding>, config: Config, ) -> Self where (T, SCL, SDA): ValidI2cConfig, { // mask all interrupts pac::ECIA.src13().write_value(1 << T::irq_bit()); pac::ECIA.en_clr13().write_value(1 << T::irq_bit()); _scl.setup(); _sda.setup(); let port = <(T, SCL, SDA) as SealedValidI2cConfig>::port(); let i2c = Self::new_inner(_peri, _scl.into(), _sda.into(), config, port); // unmask interrupt pac::ECIA.en_set13().write_value(1 << T::irq_bit()); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; i2c } /// Calls `f` to check if we are ready or not. /// If not, `g` is called once(to eg enable the required interrupts). /// The waker will always be registered prior to calling `f`. 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| { // Register prior to checking the condition T::waker().register(cx.waker()); let r = f(self); if r.is_pending() { g(self); } r }) .await } async fn wait_for_bus_free_async(&mut self) -> Result<(), Error> { self.wait_on( |_| { let compl = T::regs().compl().read(); let sts = T::regs().rsts().read(); if sts.nbb() { T::regs().compl().write(|w| w.set_idle(true)); Poll::Ready(Ok(())) } else if compl.lab() { T::regs().compl().write(|w| w.set_lab(true)); Poll::Ready(Err(Error::Abort(AbortReason::ArbitrationLoss))) } else if compl.ber() && compl.dto() { T::regs().compl().write(|w| w.set_dto(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::DeviceTimeout))) } else if compl.ber() && compl.mcto() { T::regs().compl().write(|w| w.set_mcto(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::MasterCumulativeTimeout))) } else if compl.ber() && compl.chdl() { T::regs().compl().write(|w| w.set_chdl(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::ClockHighDataLow))) } else if compl.ber() && compl.chdh() { T::regs().compl().write(|w| w.set_chdh(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::ClockHighDataHigh))) } else if compl.ber() { T::regs().compl().write(|w| w.set_ber(true)); Poll::Ready(Err(Error::Abort(AbortReason::Other))) } else { Poll::Pending } }, |_| { T::regs().cfg().modify(|w| w.set_enidi(true)); }, ) .await } async fn wait_for_completion_async(&mut self) -> Result<(), Error> { self.wait_on( |_| { let compl = T::regs().compl().read(); let sts = T::regs().rsts().read(); if sts.pin() { Poll::Pending } else if !sts.lrb_ad0() { Poll::Ready(Ok(())) } else if compl.lab() { T::regs().compl().write(|w| w.set_lab(true)); Poll::Ready(Err(Error::Abort(AbortReason::ArbitrationLoss))) } else if compl.ber() && compl.dto() { T::regs().compl().write(|w| w.set_dto(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::DeviceTimeout))) } else if compl.ber() && compl.mcto() { T::regs().compl().write(|w| w.set_mcto(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::MasterCumulativeTimeout))) } else if compl.ber() && compl.chdl() { T::regs().compl().write(|w| w.set_chdl(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::ClockHighDataLow))) } else if compl.ber() && compl.chdh() { T::regs().compl().write(|w| w.set_chdh(true)); Poll::Ready(Err(Error::BusTimeout(BusTimeoutReason::ClockHighDataHigh))) } else if compl.ber() { T::regs().compl().write(|w| w.set_ber(true)); Poll::Ready(Err(Error::Abort(AbortReason::Other))) } else { Poll::Pending } }, |_| { T::regs().cfg().modify(|w| { w.set_enidi(true); w.set_enmi(true); w.set_ensi(true); }); }, ) .await } async fn start_async(&mut self, address: u8, rw: bool, repeated: bool) -> Result<(), Error> { if address >= 0x80 { return Err(Error::AddressOutOfRange(address)); } let r = T::regs(); let own_addr = r.own_addr().read(); let (addr1, addr2) = (own_addr.addr1(), own_addr.addr2()); if address == addr1 || address == addr2 { return Err(Error::AddressReserved(address)); } if repeated { write_register_with_delay!(T::regs().wctrl(), |w| { w.set_eso(true); w.set_sta(true); w.set_ack(true); }); T::regs().i2cdata().write_value(address << 1 | u8::from(rw)); } else { if rw { T::regs().i2cdata().write_value(address << 1 | 1); self.wait_for_bus_free_async().await?; } else { self.wait_for_bus_free_async().await?; T::regs().i2cdata().write_value(address << 1 | 0); } write_register_with_delay!(T::regs().wctrl(), |w| { w.set_pin(true); w.set_eso(true); w.set_sta(true); w.set_ack(true); }); self.wait_for_completion_async().await?; } Ok(()) } async fn read_byte_async(&mut self) -> Result { self.wait_for_completion_async().await?; let byte = T::regs().i2cdata().read(); Ok(byte) } async fn write_byte_async(&mut self, byte: u8) -> Result<(), Error> { self.wait_for_completion_async().await?; T::regs().i2cdata().write_value(byte); Ok(()) } async fn read_async_internal( &mut self, address: u8, read: &mut [u8], repeated: bool, send_stop: bool, ) -> Result<(), Error> { if read.is_empty() { return Err(Error::InvalidReadBufferLength); } self.start_async(address, true, repeated).await?; // First byte in the FIFO is the slave address. Ignore it. let _ = self.read_byte_async().await?; let last = read.len() - 1; for (i, byte) in read.iter_mut().enumerate() { if i == last { write_register_with_delay!(T::regs().wctrl(), |w| { w.set_eso(true); }); } let b = self.read_byte_async().await?; *byte = b; } if send_stop { Self::stop(); } Ok(()) } async fn write_async_internal(&mut self, address: u8, write: &[u8], send_stop: bool) -> Result<(), Error> { if write.is_empty() { return Err(Error::InvalidWriteBufferLength); } self.start_async(address, false, false).await?; for byte in write.iter() { self.write_byte_async(*byte).await?; } if send_stop { Self::stop(); } Ok(()) } /// Read from address into read asynchronously. pub async fn read_async(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { self.read_async_internal(address, read, false, true).await } /// Write to address from write asynchronously. pub async fn write_async(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { self.write_async_internal(address, write, true).await } /// Write to address from write and read from address into read asynchronously. pub async fn write_read_async(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { self.write_async_internal(address, write, false).await?; self.read_async_internal(address, read, true, true).await } } /// Interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { pac::ECIA.src13().write_value(1 << T::irq_bit()); T::regs().cfg().modify(|w| { w.set_enmi(false); w.set_enidi(false); w.set_en_aas(false); }); T::waker().wake(); } } impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { fn new_inner(_peri: Peri<'d, T>, _scl: Peri<'d, AnyPin>, _sda: Peri<'d, AnyPin>, config: Config, port: u8) -> Self { let mut i2c = Self { _peri, _scl, _sda, config, port, phantom: PhantomData, }; i2c.reset_reconfigure(); i2c } fn reset_reconfigure(&mut self) { let r = T::regs(); let addr1 = self.config.addr1.unwrap_or(0); let addr2 = self.config.addr2.unwrap_or(0); critical_section::with(|_| { // Reset the controller first. T::reset(); r.cfg().write(|w| { w.set_flush_sxbuf(true); w.set_flush_srbuf(true); w.set_flush_mxbuf(true); w.set_flush_mrbuf(true); }); r.wctrl().write(|w| w.set_pin(true)); r.own_addr().write(|w| { w.set_addr1(addr1); w.set_addr2(addr2); }); r.cfg().write(|w| { if addr1 == 0 || addr2 == 0 { w.set_gc_dis(false); } w.set_port_sel(self.port); w.set_fen(true); }); match self.config.speed { BusSpeed::Standard => { r.busclk().write(|w| { w.set_low_per(0x4f); w.set_high_per(0x4f); }); r.datatm().write(|w| { w.set_data_hold(0x06); w.set_restart_setup(0x50); w.set_stop_setup(0x4d); w.set_first_start_hold(0x0c); }); r.rshtm().write(|w| w.set_rshtm(0x4d)); r.idlsc().write(|w| { w.set_fair_bus_idl_min(0x01ed); w.set_fair_idl_dly(0x01fc); }); r.tmoutsc().write(|w| { w.set_clk_high_tim_out(0xc7); w.set_slv_cum_tim_out(0xc2); w.set_mast_cum_tim_out(0x9c); w.set_bus_idle_min(0x4b); }); } BusSpeed::Fast => { r.busclk().write(|w| { w.set_low_per(0x17); w.set_high_per(0x0f); }); r.datatm().write(|w| { w.set_data_hold(0x06); w.set_restart_setup(0x0a); w.set_stop_setup(0x0a); w.set_first_start_hold(0x04); }); r.rshtm().write(|w| w.set_rshtm(0x0a)); r.idlsc().write(|w| { w.set_fair_bus_idl_min(0x0050); w.set_fair_idl_dly(0x0100); }); r.tmoutsc().write(|w| { w.set_clk_high_tim_out(0xc7); w.set_slv_cum_tim_out(0xc2); w.set_mast_cum_tim_out(0x9c); w.set_bus_idle_min(0x15); }); } BusSpeed::FastPlus => { r.busclk().write(|w| { w.set_low_per(0x09); w.set_high_per(0x05); }); r.datatm().write(|w| { w.set_data_hold(0x01); w.set_restart_setup(0x06); w.set_stop_setup(0x06); w.set_first_start_hold(0x04); }); r.rshtm().write(|w| w.set_rshtm(0x06)); r.idlsc().write(|w| { w.set_fair_bus_idl_min(0x0050); w.set_fair_idl_dly(0x0100); }); r.tmoutsc().write(|w| { w.set_clk_high_tim_out(0xc7); w.set_slv_cum_tim_out(0xc2); w.set_mast_cum_tim_out(0x9c); w.set_bus_idle_min(0x08); }); } } r.wctrl().write(|w| { w.set_pin(true); w.set_eso(true); w.set_ack(true); }); r.cfg().modify(|w| w.set_en(true)); }); while !r.rsts().read().nbb() {} // 6. Delay. // // Documentation states: Wait a time equal to the longest i2c // message to synchronize the NBB bit in multi-master systems // only. // // We're assuming that we're not in a multi-master scenario, // therefore skipping the delay. } fn wait_for_bus_free() { while !T::regs().rsts().read().nbb() {} } fn start(address: u8, rw: bool, repeated: bool) -> Result<(), Error> { if address >= 0x80 { return Err(Error::AddressOutOfRange(address)); } let r = T::regs(); let own_addr = r.own_addr().read(); let (addr1, addr2) = (own_addr.addr1(), own_addr.addr2()); if address == addr1 || address == addr2 { return Err(Error::AddressReserved(address)); } if repeated { write_register_with_delay!(T::regs().wctrl(), |w| { w.set_eso(true); w.set_sta(true); w.set_ack(true); }); T::regs().i2cdata().write_value(address << 1 | u8::from(rw)); } else { if rw { T::regs().i2cdata().write_value(address << 1 | 1); Self::wait_for_bus_free(); } else { Self::wait_for_bus_free(); T::regs().i2cdata().write_value(address << 1 | 0); } write_register_with_delay!(T::regs().wctrl(), |w| { w.set_pin(true); w.set_eso(true); w.set_sta(true); w.set_ack(true); }); } Ok(()) } fn stop() { write_register_with_delay!(T::regs().wctrl(), |w| { w.set_pin(true); w.set_eso(true); w.set_sto(true); w.set_sta(false); w.set_ack(true); }); } fn check_status() -> Result<(), Error> { while T::regs().rsts().read().pin() {} let status = T::regs().rsts().read(); if status.lrb_ad0() { Self::stop(); Err(Error::Abort(AbortReason::NoAcknowledge)) } else if status.lab() { Err(Error::Abort(AbortReason::ArbitrationLoss)) } else if status.ber() { let completion = T::regs().compl().read(); if completion.dto() { Err(Error::BusTimeout(BusTimeoutReason::DeviceTimeout)) } else if completion.mcto() { Err(Error::BusTimeout(BusTimeoutReason::MasterCumulativeTimeout)) } else if completion.chdl() { Err(Error::BusTimeout(BusTimeoutReason::ClockHighDataLow)) } else if completion.chdh() { Err(Error::BusTimeout(BusTimeoutReason::ClockHighDataHigh)) } else { Err(Error::Abort(AbortReason::Other)) } } else { Ok(()) } } fn read_byte(last: bool) -> Result { if !last { Self::check_status()?; } else { write_register_with_delay!(T::regs().wctrl(), |w| { w.set_eso(true); }); } let byte = T::regs().i2cdata().read(); if last { Self::check_status()?; } Ok(byte) } fn write_byte(byte: u8) -> Result<(), Error> { Self::check_status()?; T::regs().i2cdata().write_value(byte); Ok(()) } fn read_blocking_internal( &mut self, address: u8, read: &mut [u8], repeated: bool, send_stop: bool, ) -> Result<(), Error> { if read.is_empty() { return Err(Error::InvalidReadBufferLength); } Self::start(address, true, repeated)?; // First byte in the FIFO is the slave address. Ignore it. let _ = Self::read_byte(false)?; let last = read.len() - 1; for (i, byte) in read.iter_mut().enumerate() { *byte = Self::read_byte(i == last)?; } if send_stop { Self::stop(); } Ok(()) } fn write_blocking_internal(&mut self, address: u8, write: &[u8], send_stop: bool) -> Result<(), Error> { if write.is_empty() { return Err(Error::InvalidWriteBufferLength); } Self::start(address, false, false)?; for byte in write.iter() { Self::write_byte(*byte)?; } if send_stop { Self::stop(); } Ok(()) } /// Read from address into read blocking caller until done. pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { // TODO - empirically determined delay cortex_m::asm::delay(20_000); let retval = self.read_blocking_internal(address, read, false, true); retval } /// Write to address from write blocking caller until done. pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { // TODO - empirically determined delay cortex_m::asm::delay(20_000); let retval = self.write_blocking_internal(address, write, true); retval } /// Write to address from write and read from address into read blocking caller until done. pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { // TODO - empirically determined delay cortex_m::asm::delay(20_000); self.write_blocking_internal(address, write, false)?; // TODO - empirically determined delay cortex_m::asm::delay(20_000); self.read_blocking_internal(address, read, true, true) } } 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> { 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(address, buf, false, last)? } embedded_hal_02::blocking::i2c::Operation::Write(buf) => { self.write_blocking_internal(address, 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::Other) => embedded_hal_1::i2c::ErrorKind::Other, Self::BusTimeout(_) => embedded_hal_1::i2c::ErrorKind::Bus, 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> { for i in 0..operations.len() { let last = i == operations.len() - 1; match &mut operations[i] { embedded_hal_1::i2c::Operation::Read(buf) => self.write_blocking_internal(address, buf, last)?, embedded_hal_1::i2c::Operation::Write(buf) => self.write_blocking_internal(address, 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.into(), read).await } async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { self.write_async(address.into(), write).await } async fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { self.write_read_async(address.into(), write, 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: u8 = address.into(); let mut iterator = operations.iter_mut(); while let Some(op) = iterator.next() { let last = iterator.len() == 0; match op { Operation::Read(buffer) => { self.read_async_internal(addr, buffer, false, last).await?; } Operation::Write(buffer) => { self.write_async_internal(addr, buffer, last).await?; } } } Ok(()) } } trait SealedInstance { fn regs() -> smb0::Smb0; fn waker() -> &'static AtomicWaker; fn irq_bit() -> usize; fn reset(); } trait SealedMode {} /// Drivermode. #[allow(private_bounds)] pub trait Mode: SealedMode {} macro_rules! impl_mode { ($mode:ident) => { impl SealedMode for $mode {} impl Mode for $mode {} }; } /// 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 + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } // Ideally, these should be part of a dedicated PCR driver const LOCK: u32 = 0xa638_2d4d; const UNLOCK: u32 = 0xa638_2d4c; macro_rules! impl_instance { ($peri:ident, $bit:expr, $irq:ident, $reset:expr) => { impl SealedInstance for peripherals::$peri { #[inline(always)] fn regs() -> smb0::Smb0 { pac::$peri } #[inline(always)] fn waker() -> &'static AtomicWaker { static WAKER: AtomicWaker = AtomicWaker::new(); &WAKER } #[inline(always)] fn irq_bit() -> usize { $bit } #[inline(always)] fn reset() { fn internal_reset(f: impl FnOnce(pac::pcr::Pcr)) { pac::PCR.lock_reg().write(|w| w.set_pcr_rst_en_lock(UNLOCK)); f(pac::PCR); pac::PCR.lock_reg().write(|w| w.set_pcr_rst_en_lock(LOCK)); } internal_reset($reset); } } impl Instance for peripherals::$peri { type Interrupt = crate::interrupt::typelevel::$irq; } }; } impl_instance!(SMB0, 0, I2CSMB0, |pcr| pcr .rst_en_1() .write(|w| w.set_smb0_rst_en(true))); impl_instance!(SMB1, 1, I2CSMB1, |pcr| pcr .rst_en_3() .write(|w| w.set_smb1_rst_en(true))); impl_instance!(SMB2, 2, I2CSMB2, |pcr| pcr .rst_en_3() .write(|w| w.set_smb2_rst_en(true))); impl_instance!(SMB3, 3, I2CSMB3, |pcr| pcr .rst_en_3() .write(|w| w.set_smb3_rst_en(true))); impl_instance!(SMB4, 4, I2CSMB4, |pcr| pcr .rst_en_3() .write(|w| w.set_smb_4_rst_en(true))); /// SDA pin. pub trait SdaPin: crate::gpio::Pin { fn setup(&self); } /// SCL pin. pub trait SclPin: crate::gpio::Pin { fn setup(&self); } macro_rules! impl_pin { ($function:ident, $($pin:ident, $mux:ident),*) => { $( impl $function for peripherals::$pin { #[inline(always)] fn setup(&self) { critical_section::with(|_| { self.regs().ctrl1.modify(|w| { w.set_out_buff_type(crate::pac::BufferType::OPEN_DRAIN); w.set_inp_dis(false); w.set_alt_data(true); w.set_mux_ctrl(crate::pac::Function::$mux); }) }); } } )* }; } #[rustfmt::skip] impl_pin!( SdaPin, GPIO0, F3, GPIO3, F1, GPIO5, F1, GPIO7, F1, GPIO12, F1, GPIO26, F3, GPIO30, F2, GPIO66, F2, GPIO70, F2, GPIO72, F2, GPIO130, F1, GPIO132, F1, GPIO141, F1, GPIO143, F1, GPIO145, F1, GPIO147, F1, GPIO152, F3, GPIO154, F1, GPIO231, F1 ); #[rustfmt::skip] impl_pin!( SclPin, GPIO4, F1, GPIO6, F1, GPIO10, F1, GPIO13, F1, GPIO24, F3, GPIO27, F3, GPIO62, F2, GPIO65, F2, GPIO71, F2, GPIO73, F2, GPIO107, F4, GPIO131, F1, GPIO140, F1, GPIO142, F1, GPIO144, F4, GPIO146, F1, GPIO150, F1, GPIO155, F1, GPIO230, F1 ); trait SealedValidI2cConfig { fn port() -> u8; } /// A marker trait implemented for valid configurations #[allow(private_bounds)] pub trait ValidI2cConfig: SealedValidI2cConfig {} macro_rules! impl_config { ($scl:ident, $sda:ident, $port:expr) => { impl SealedValidI2cConfig for (peripherals::SMB0, peripherals::$scl, peripherals::$sda) { #[inline(always)] fn port() -> u8 { $port } } impl SealedValidI2cConfig for (peripherals::SMB1, peripherals::$scl, peripherals::$sda) { #[inline(always)] fn port() -> u8 { $port } } impl SealedValidI2cConfig for (peripherals::SMB2, peripherals::$scl, peripherals::$sda) { #[inline(always)] fn port() -> u8 { $port } } impl SealedValidI2cConfig for (peripherals::SMB3, peripherals::$scl, peripherals::$sda) { #[inline(always)] fn port() -> u8 { $port } } impl SealedValidI2cConfig for (peripherals::SMB4, peripherals::$scl, peripherals::$sda) { #[inline(always)] fn port() -> u8 { $port } } impl ValidI2cConfig for (peripherals::SMB0, peripherals::$scl, peripherals::$sda) {} impl ValidI2cConfig for (peripherals::SMB1, peripherals::$scl, peripherals::$sda) {} impl ValidI2cConfig for (peripherals::SMB2, peripherals::$scl, peripherals::$sda) {} impl ValidI2cConfig for (peripherals::SMB3, peripherals::$scl, peripherals::$sda) {} impl ValidI2cConfig for (peripherals::SMB4, peripherals::$scl, peripherals::$sda) {} }; } // I2C00 impl_config!(GPIO4, GPIO3, 0); // I2C01 impl_config!(GPIO73, GPIO72, 1); impl_config!(GPIO131, GPIO130, 1); // I2C02 impl_config!(GPIO155, GPIO154, 2); // I2C03 impl_config!(GPIO10, GPIO7, 3); // I2C04 impl_config!(GPIO144, GPIO143, 4); // I2C05 impl_config!(GPIO142, GPIO141, 5); // I2C06 impl_config!(GPIO140, GPIO132, 6); // I2C07 impl_config!(GPIO13, GPIO12, 7); impl_config!(GPIO24, GPIO152, 7); // I2C08 impl_config!(GPIO230, GPIO231, 8); // I2C09 impl_config!(GPIO146, GPIO145, 9); // I2C10 impl_config!(GPIO107, GPIO30, 10); // I2C11 impl_config!(GPIO62, GPIO0, 11); impl_config!(GPIO6, GPIO5, 11); // I2C12 impl_config!(GPIO27, GPIO26, 12); // I2C13 impl_config!(GPIO65, GPIO66, 13); // I2C14 impl_config!(GPIO71, GPIO70, 14); // I2C15 impl_config!(GPIO150, GPIO147, 15); ================================================ FILE: embassy-microchip/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![doc = include_str!("../README.md")] //! ## Feature flags #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] #[cfg(not(any( feature = "mec1721n_b0_lj", feature = "mec1721n_b0_sz", feature = "mec1723n_b0_lj", feature = "mec1723n_b0_sz", feature = "mec1723n_f0_sz", feature = "mec1723n_p0_9y", feature = "mec1724n_b0_lj", feature = "mec1724n_b0_sz", feature = "mec1725n_b0_lj", feature = "mec1727n_b0_sz", )))] compile_error!( "No chip feature activated. You must activate exactcly one of the following features: mec1721n_b0_lj, mec1721n_b0_sz, mec1723n_b0_lj, mec1723n_b0_sz, mec1723n_f0_sz, mec1723n_p0_9y, mec1724n_b0_lj, mec1724n_b0_sz, mec1725n_b0_lj, mec1727n_b0_sz, " ); // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; pub mod gpio; pub mod i2c; pub mod pwm; pub mod tach; pub mod time_driver; pub mod uart; // Reexports pub use embassy_hal_internal::{Peri, PeripheralType}; #[cfg(feature = "unstable-pac")] pub use mec17xx_pac as pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use mec17xx_pac as pac; #[cfg(feature = "rt")] pub use crate::pac::NVIC_PRIO_BITS; embassy_hal_internal::interrupt_mod!( ADC_RPT, ADC_SNGL, AEC0_IBF, AEC0_OBE, AEC1_IBF, AEC1_OBE, AEC2_IBF, AEC2_OBE, AEC3_IBF, AEC3_OBE, AEC4_IBF, AEC4_OBE, APM1_CTL, APM1_EN, APM1_STS, ASIF, BCM_BUSY_CLR_0, BCM_ERR_0, CCT, CCT_CAP0, CCT_CAP1, CCT_CAP2, CCT_CAP3, CCT_CAP4, CCT_CAP5, CCT_CMP0, CCT_CMP1, CNTR_TMR0, CNTR_TMR1, CNTR_TMR2, CNTR_TMR3, DMA_CH00, DMA_CH01, DMA_CH02, DMA_CH03, DMA_CH04, DMA_CH05, DMA_CH06, DMA_CH07, DMA_CH08, DMA_CH09, DMA_CH10, DMA_CH11, DMA_CH12, DMA_CH13, DMA_CH14, DMA_CH15, EMI0, EMI1, EMI2, ESPI_RESET, ESPI_VWIRE, GIRQ08, GIRQ09, GIRQ10, GIRQ11, GIRQ12, GIRQ13, GIRQ14, GIRQ15, GIRQ17, GIRQ18, GIRQ19, GIRQ20, GIRQ21, GIRQ23, GIRQ24, GIRQ25, GIRQ26, HTMR0, HTMR1, I2CSMB0, I2CSMB1, I2CSMB2, I2CSMB3, I2CSMB4, INTR_BM1, INTR_BM2, INTR_FLASH, INTR_LTR, INTR_OOB_DOWN, INTR_OOB_UP, INTR_PC, KBC_IBF, KBC_OBE, KEYSCAN, LED0, LED1, LED2, LED3, MBOX, P80CAP0, PECI, PHOT, POWERGUARD_0, POWERGUARD_1, PS2_0A_WAKE, PS2_0B_WAKE, PS2_0_ACT, QMSPI, RC_ID0, RC_ID1, RC_ID2, RPM2PWM_0_SPIN, RPM2PWM_0_STALL, RPM2PWM_1_SPIN, RPM2PWM_1_STALL, RTC, RTC_ALARM, RTMR, RX0, RX1, SAF_DONE, SAF_ERR, SPISLV, SYSPWR, TACH0, TACH1, TACH2, TACH3, TIMER16_0, TIMER16_1, TIMER16_2, TIMER16_3, TIMER32_0, TIMER32_1, TX0, TX1, UART0, UART1, VCI_IN0, VCI_IN1, VCI_IN2, VCI_IN3, VCI_OVRD_IN, WDT, WK, WKSEC, WKSUB, WKSUBSEC, ); /// 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_microchip::{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)] #[unsafe(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)* } } embassy_hal_internal::peripherals! { // Port 0 GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, // Port 1 GPIO10, GPIO11, GPIO12, GPIO13, GPIO14, GPIO15, GPIO16, GPIO17, // Port 2 GPIO20, GPIO21, GPIO22, GPIO23, GPIO24, GPIO25, GPIO26, GPIO27, // Port 3 GPIO30, GPIO31, GPIO32, GPIO33, GPIO34, GPIO35, GPIO36, // Port 4 GPIO40, GPIO41, GPIO42, GPIO43, GPIO44, GPIO45, GPIO46, GPIO47, // Port 5 GPIO50, GPIO51, GPIO52, GPIO53, GPIO54, GPIO55, GPIO56, GPIO57, // Port 6 GPIO60, GPIO61, GPIO62, GPIO63, GPIO64, GPIO65, GPIO66, GPIO67, // Port 7 GPIO70, GPIO71, GPIO72, GPIO73, GPIO74, GPIO75, GPIO76, // Ports 8 and 9 don't exist // Port 10 GPIO100, GPIO101, GPIO102, GPIO103, GPIO104, GPIO105, GPIO106, GPIO107, // Port 11 GPIO112, GPIO113, GPIO114, GPIO115, GPIO116, GPIO117, // Port 12 GPIO120, GPIO121, GPIO122, GPIO123, GPIO124, GPIO125, GPIO126, GPIO127, // Port 13 GPIO130, GPIO131, GPIO132, GPIO133, GPIO134, GPIO135, // Port 14 GPIO140, GPIO141, GPIO142, GPIO143, GPIO144, GPIO145, GPIO146, GPIO147, // Port 15 GPIO150, GPIO151, GPIO152, GPIO153, GPIO154, GPIO155, GPIO156, GPIO157, // Port 16 GPIO160, GPIO161, GPIO162, GPIO165, GPIO166, GPIO167, // Port 17 GPIO170, GPIO171, GPIO172, GPIO173, GPIO174, GPIO175, // Ports 18 and 19 don't exist // Port 20 GPIO200, GPIO201, GPIO202, GPIO203, GPIO204, GPIO205, GPIO206, GPIO207, // Port 21 GPIO210, GPIO211, GPIO212, GPIO213, GPIO214, GPIO215, GPIO216, GPIO217, // Port 22 GPIO221, GPIO222, GPIO223, GPIO224, GPIO225, GPIO226, GPIO227, // Port 23 GPIO230, GPIO231, // Port 24 GPIO240, GPIO241, GPIO242, GPIO243, GPIO244, GPIO245, GPIO246, GPIO247, // Port 25 // GPIO253, no interrupt GPIO254, GPIO255, // GPIO256, no interrupt // GPIO257, no interrupt // Port 26 // GPIO260, no interrupt ESPI, PWM0, PWM1, PWM2, PWM3, PWM4, PWM5, PWM6, PWM7, PWM8, PWM9, PWM10, PWM11, SMB0, SMB1, SMB2, SMB3, SMB4, TACH0, TACH1, TACH2, TACH3, UART0, UART1, } /// HAL configuration for Microchip. pub mod config { /// HAL configuration passed when initializing. #[non_exhaustive] pub struct Config {} impl Default for Config { fn default() -> Self { Self {} } } impl Config { /// Create a new configuration pub fn new() -> Self { Self {} } } } /// Initialize the `embassy-microchip` 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 panis. pub fn init(_config: config::Config) -> Peripherals { let peripherals = Peripherals::take(); unsafe { // ROM code leaves interrupts globally disabled, d'oh. cortex_m::interrupt::enable(); gpio::init(); } time_driver::init(); peripherals } ================================================ FILE: embassy-microchip/src/pwm.rs ================================================ //! Pulse Width Modulation (PWM) use embassy_hal_internal::{Peri, PeripheralType}; pub use embedded_hal_1::pwm::SetDutyCycle; use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; use crate::gpio::{AnyPin, Pin, SealedPin}; use crate::pac::pwm0::Pwm0; use crate::{pac, peripherals}; #[allow(non_camel_case_types)] #[derive(Copy, Clone, PartialEq)] /// Clock selection pub enum Clock { _48MHz, _100kHz, } impl From for bool { fn from(clk: Clock) -> Self { match clk { Clock::_48MHz => true, Clock::_100kHz => false, } } } /// The configuration of a PWM slice. #[non_exhaustive] #[derive(Clone, Copy)] pub struct Config { /// Inverts the PWM output signal. pub invert: bool, /// Enables the PWM slice, allowing it to generate an output. pub enable: bool, /// PWM target frequency pub frequency: u32, /// PWM target duty cycle pub duty_cycle: u16, } impl Default for Config { fn default() -> Self { Self { invert: false, enable: true, // differs from reset frequency: 100_000, duty_cycle: 32767, } } } /// 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, T: Instance> { _peri: Peri<'d, T>, _pin: Peri<'d, AnyPin>, } impl<'d, T: Instance> ErrorType for Pwm<'d, T> { type Error = PwmError; } impl<'d, T: Instance> Pwm<'d, T> { const HIGHEST_DIVIDER: u32 = 15; const HIGHEST_ON_OFF: u32 = 65_535; const HIGH_CLK_FREQ: u32 = 48_000_000; const LOW_CLK_FREQ: u32 = 100_000; const MIN_HIGH_CLK_FREQ: u32 = Self::HIGH_CLK_FREQ / (2 * (Self::HIGHEST_ON_OFF + 1) * (Self::HIGHEST_DIVIDER + 1)); pub fn new(peri: Peri<'d, T>, pin: Peri<'d, impl PwmPin>, config: Config) -> Self { Self::new_inner(peri, pin, config) } fn new_inner(_peri: Peri<'d, T>, _pin: Peri<'d, impl PwmPin>, config: Config) -> Self { _pin.setup(); Self::configure(config); Self { _peri, _pin: _pin.into(), } } fn configure(config: Config) { let (div, on, off, clk) = Self::compute_parameters(config.frequency, config.duty_cycle); T::regs().cfg().write(|w| { w.set_inv(config.invert); w.set_clk_sel(clk.into()); w.set_clk_pre_div(div); w.set_pwm_en(config.enable); }); T::regs().cnt_on().write_value(on); T::regs().cnt_off().write_value(off); } fn compute_parameters(target_freq: u32, target_duty_cycle: u16) -> (u8, u32, u32, Clock) { let high = target_freq > Self::MIN_HIGH_CLK_FREQ; // We assume that if target_freq is not greater than high // clock minimum frequency, then it must fit within the low // clock, even if with high error. let (clk, freq) = if high { (Clock::_48MHz, Self::HIGH_CLK_FREQ) } else { (Clock::_100kHz, Self::LOW_CLK_FREQ) }; let (div, _) = (1..=16).fold((0, u32::MAX), |(best_div, best_error), d| { let candidate = freq / d; let error = target_freq.abs_diff(candidate); if error < best_error { (candidate, error) } else { (best_div, best_error) } }); let (on, off) = Self::compute_on_off(freq, target_freq, target_duty_cycle); (div as u8, on, off, clk) } fn compute_on_off(freq: u32, target_freq: u32, target_duty_cycle: u16) -> (u32, u32) { let total = freq * 10 / target_freq; let on = ((total * u32::from(target_duty_cycle)) / 100_000) - 1; let off = total - on - 2; (on, off) } } impl<'d, T: Instance> SetDutyCycle for Pwm<'d, T> { fn max_duty_cycle(&self) -> u16 { u16::MAX } 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 off = u32::from(duty) * u32::from(u16::MAX) / u32::from(max_duty); let on = u32::from(u16::MAX) - off; T::regs().cnt_on().write_value(on.into()); T::regs().cnt_off().write_value(off.into()); Ok(()) } } trait SealedInstance { fn regs() -> Pwm0; } /// PWM Instance trait #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send {} macro_rules! impl_instance { ($($peri:ident),*) => { $( impl SealedInstance for peripherals::$peri { #[inline(always)] fn regs() -> Pwm0 { pac::$peri } } impl Instance for peripherals::$peri {} )* } } impl_instance!(PWM0, PWM1, PWM2, PWM3, PWM4, PWM5, PWM6, PWM7, PWM8, PWM9, PWM10, PWM11); pub trait PwmPin: Pin + PeripheralType { fn setup(&self); } macro_rules! impl_pin { ($peri:ident, $($pin:ident),*) => { $( impl PwmPin for peripherals::$pin { #[inline(always)] fn setup(&self) { critical_section::with(|_| { self.regs().ctrl1.modify(|w| { w.set_mux_ctrl(pac::Function::F1); }) }); } } )* } } impl_pin!(PWM0, GPIO53, GPIO241); impl_pin!(PWM1, GPIO54); impl_pin!(PWM2, GPIO55, GPIO45); impl_pin!(PWM3, GPIO56); impl_pin!(PWM4, GPIO11, GPIO1); impl_pin!(PWM5, GPIO2); impl_pin!(PWM6, GPIO14, GPIO63); impl_pin!(PWM7, GPIO15); impl_pin!(PWM8, GPIO35, GPIO175); impl_pin!(PWM9, GPIO133); impl_pin!(PWM10, GPIO134); impl_pin!(PWM11, GPIO160, GPIO222); ================================================ FILE: embassy-microchip/src/tach.rs ================================================ //! TACH driver. use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::typelevel::{Binding, Interrupt}; use crate::pac::tach0::Tach0; use crate::{interrupt, pac, peripherals}; // The minimum and maximum RPMs the TACH peripheral can reliably measure according to datasheet const MIN_RPM: u32 = 100; const MAX_RPM: u32 = 30_000; // The fixed clock to TACH const TACH_CLK: u32 = 100_000; // Seconds per minute const SEC_PER_MIN: u32 = 60; /// Tach interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { // Disable the interrupt here to prevent storm, so we can read the count ready bit and clear it later T::regs().ctrl().modify(|w| w.set_cnt_rdy_int_en(false)); T::waker().wake(); } } /// TACH error. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// The calculated RPM is below the minimum RPM the tach peripheral can reliably measure (100 RPM), /// thus it is likely inaccurate. /// /// It is very likely the fan is stopped, however this driver does not assume that. RpmLow(u32), /// The calculated RPM is above the maximum RPM the tach peripheral can reliably measure (30,000 RPM), /// thus it is likely inaccurate. RpmHigh(u32), /// Calculating RPM would result in a division by zero. /// /// This would indicate the selected number of [`Edges`] occurred in less than one tach clock period (10 µs), /// thus the tach counter is zero. DivideByZero, } /// The number of TACH input edges for which the number of 100 kHz pulses will be counted. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Edges { /// Two edges. _2, /// Three edges. _3, /// Five edges. _5, /// Nine edges. _9, } impl From for u8 { fn from(edges: Edges) -> Self { match edges { Edges::_2 => 0b00, Edges::_3 => 0b01, Edges::_5 => 0b10, Edges::_9 => 0b11, } } } /// TACH peripheral config. pub struct Config { /// Remove high frequency glitches from TACH input /// (e.g. pulses less than two 100 KHz periods wide will be filtered). /// /// It is recommended to always enable this. pub filter_en: bool, /// The number of TACH input edges for which the number of 100 kHz pulses will be counted. /// /// This should match the number of edges per revolution for specific fan model. /// /// For typical fans which produce a 50% duty cycle square wave, /// five edges (two periods) represents one revolution. /// /// See figure 28-2 in datasheet. pub edges: Edges, } impl Default for Config { fn default() -> Self { Self { filter_en: true, edges: Edges::_5, } } } /// TACH driver. pub struct Tach<'d, T: Instance> { _peri: Peri<'d, T>, _pin: Peri<'d, AnyPin>, } impl<'d, T: Instance> Tach<'d, T> { /// Create a new TACH driver instance. pub fn new( _peri: Peri<'d, T>, _pin: Peri<'d, impl TachPin>, _irq: impl Binding>, config: Config, ) -> Self { // Sets given pin to TACH mode (alternate function 1) // REVISIT: make an API for this critical_section::with(|_| { _pin.regs().ctrl1.modify(|w| { w.set_mux_ctrl(pac::Function::F1); }) }); // Set tach to mode 1 with edges and filtering set by config, then enable. // // Mode 0 does not seem very useful for a high level driver like this, // so users are free to use the PAC if they need it. T::regs().ctrl().write(|w| { w.set_en(true); w.set_filt_en(config.filter_en); w.set_rd_mod_sel(true); w.set_edges(config.edges.into()); }); T::Interrupt::unpend(); // SAFETY: We have sole control of TACH interrupts so this is safe to do. unsafe { T::Interrupt::enable() }; Self { _peri, _pin: _pin.into(), } } fn calculate_rpm(&self) -> Result { let count = T::regs().ctrl().read().cntr(); if count == 0 { // Somehow we saw all edges before a single 100 kHz pulse? Err(Error::DivideByZero) } else { // This calculation assumes the user correctly set the number of edges per one revolution let rpm = (SEC_PER_MIN * TACH_CLK) / count as u32; // The datasheet specifies the peripheral can reliably measure 100-30,000 RPM // If we calculate a value outside that range, still return it, but wrap in an error to let user know match rpm { ..MIN_RPM => Err(Error::RpmLow(rpm)), MIN_RPM..=MAX_RPM => Ok(rpm as u16), _ => Err(Error::RpmHigh(rpm)), } } } /// Return the measured fan speed in revolutions per minute (RPM). /// /// # Errors /// /// If calculated RPM is below 100 RPM, [`Error::RpmLow`] is returned. In this case, /// the fan is likely stopped. /// /// If calculated RPM is above 30,000 RPM, [`Error::RpmHigh`] is returned. In this case, /// the fan is rotating too quickly and the calculated RPM is likely inaccurate. /// /// If selected number of [`Edges`] occurred in less than one TACH clock period (10 µs), /// [`Error::DivideByZero`] is returned as calculating RPM is impossible. pub async fn rpm(&mut self) -> Result { poll_fn(|cx| { T::waker().register(cx.waker()); if T::regs().sts().read().cnt_rdy_sts() { // Count has been latched, so clear ready bit and calculate RPM from count T::regs().sts().modify(|w| w.set_cnt_rdy_sts(true)); Poll::Ready(self.calculate_rpm()) } else { // Count still not latched, so enable interrupt and wait T::regs().ctrl().modify(|w| w.set_cnt_rdy_int_en(true)); Poll::Pending } }) .await } } impl<'d, T: Instance> Drop for Tach<'d, T> { fn drop(&mut self) { T::regs().ctrl().write(|w| w.set_en(false)); T::Interrupt::disable(); } } trait SealedInstance { fn regs() -> Tach0; fn waker() -> &'static AtomicWaker; } /// TACH Instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_instance { ($peri:ident) => { impl SealedInstance for peripherals::$peri { #[inline(always)] fn regs() -> Tach0 { pac::$peri } #[inline(always)] fn waker() -> &'static AtomicWaker { static WAKER: AtomicWaker = AtomicWaker::new(); &WAKER } } impl Instance for peripherals::$peri { type Interrupt = crate::interrupt::typelevel::$peri; } }; } impl_instance!(TACH0); impl_instance!(TACH1); impl_instance!(TACH2); impl_instance!(TACH3); /// A GPIO pin that can be configured as a TACH pin. pub trait TachPin: GpioPin + PeripheralType {} macro_rules! impl_pin { ($peri:ident, $($pin:ident),*) => { $( impl TachPin for peripherals::$pin {} )* } } impl_pin!(TACH0, GPIO50); impl_pin!(TACH1, GPIO51); impl_pin!(TACH2, GPIO52); impl_pin!(TACH3, GPIO33); ================================================ FILE: embassy-microchip/src/time_driver.rs ================================================ //! Time driver. use core::cell::{Cell, RefCell}; use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; use critical_section::CriticalSection; use embassy_hal_internal::interrupt::InterruptExt; use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_time_driver::Driver; use embassy_time_queue_utils::Queue; use crate::interrupt; use crate::pac::{CCT, ECIA}; /// Calculate the timestamp from the period count and the tick count. /// /// The Input Capture and Compare Timer is a 32-bit free running timer /// running at the main clock frequency. /// /// We define a period to be 2^31 ticks, such that each overflow is 2 /// periods. /// /// Toe 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 mean 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 32-bit integer, it overlows on 2^32 * 2^31 / /// 48_000_000 seconds of uptime, which is about 6093 years. fn calc_now(period: u32, counter: u32) -> u64 { ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) 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 CctDriver { /// Number of 2^31 periods elapsed since boot. period: AtomicU32, /// Timestamp at which to fire alarm. `u64::MAX` if no alarm is /// scheduled. alarm: Mutex, queue: Mutex>, } embassy_time_driver::time_driver_impl!(static DRIVER: CctDriver = CctDriver { period: AtomicU32::new(0), alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), queue: Mutex::new(RefCell::new(Queue::new())), }); impl CctDriver { fn init(&'static self) { interrupt::CCT.disable(); interrupt::CCT_CMP0.disable(); interrupt::CCT_CMP1.disable(); interrupt::CCT.set_priority(interrupt::Priority::P0); interrupt::CCT_CMP0.set_priority(interrupt::Priority::P0); interrupt::CCT_CMP1.set_priority(interrupt::Priority::P0); ECIA.src18().write_value(1 << 20 | 1 << 27 | 1 << 28); ECIA.en_set18().write_value(1 << 20 | 1 << 27 | 1 << 28); // Reset timer CCT.ctrl().write(|w| w.set_free_rst(true)); // Wait until reset is completed while CCT.ctrl().read().free_rst() {} // Mid CCT.comp0().write(|w| w.set_comp_0(0x8000_0000)); CCT.ctrl().write(|w| { w.set_act(true); w.set_free_en(true); w.set_cmp_en0(true); }); interrupt::CCT.unpend(); interrupt::CCT_CMP0.unpend(); interrupt::CCT_CMP1.unpend(); unsafe { interrupt::CCT.enable(); interrupt::CCT_CMP0.enable(); interrupt::CCT_CMP1.enable(); } } fn next_period(&self) { let period = self.period.load(Ordering::Relaxed) + 1; self.period.store(period, Ordering::Relaxed); let t = (period as u64) << 31; critical_section::with(move |cs| { let alarm = self.alarm.borrow(cs); let at = alarm.timestamp.get(); if at < t + 0xc000_0000 { CCT.ctrl().modify(|w| w.set_cmp_en1(true)); } }); } fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { self.alarm.borrow(cs).timestamp.set(timestamp); let t = self.now(); if timestamp <= t { // If timestamp has passed, alarm will not fire. // Disarm it and return `false`. CCT.ctrl().modify(|w| w.set_cmp_en1(false)); self.alarm.borrow(cs).timestamp.set(u64::MAX); return false; } CCT.comp1().write(|w| w.set_comp_1(timestamp as u32)); let diff = timestamp - t; CCT.ctrl().modify(|w| w.set_cmp_en1(diff < 0xc000_0000)); let t = self.now(); if timestamp <= t { CCT.ctrl().modify(|w| w.set_cmp_en1(true)); // If timestamp has passed, alarm will not fire. // Disarm it and return `false`. CCT.ctrl().modify(|w| w.set_cmp_en1(false)); self.alarm.borrow(cs).timestamp.set(u64::MAX); return false; } 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()); } } } impl Driver for CctDriver { fn now(&self) -> u64 { let period = self.period.load(Ordering::Relaxed); compiler_fence(Ordering::Acquire); let counter = CCT.free_run().read().tmr(); 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 = "rt")] #[interrupt] fn CCT() { ECIA.src18().write_value(1 << 20); DRIVER.next_period(); } #[cfg(feature = "rt")] #[interrupt] fn CCT_CMP0() { ECIA.src18().write_value(1 << 27); DRIVER.next_period(); } #[cfg(feature = "rt")] #[interrupt] fn CCT_CMP1() { ECIA.src18().write_value(1 << 28); critical_section::with(|cs| DRIVER.trigger_alarm(cs)); } pub(crate) fn init() { DRIVER.init() } ================================================ FILE: embassy-microchip/src/uart.rs ================================================ //! UART driver. use core::future::poll_fn; use core::marker::PhantomData; use core::sync::atomic::{AtomicU8, Ordering}; use core::task::Poll; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use mec17xx_pac::uart0::regs::DataLsr; use crate::gpio::{AnyPin, Pin, SealedPin}; use crate::interrupt::typelevel::{Binding, Interrupt}; use crate::pac::uart0::Uart0; use crate::{interrupt, pac, peripherals}; // The datasheet does not appear to specify this, but the 16550 has a 16-byte FIFO // and empirical testing confirms this is also the case for the MEC UART. // // This is useful to know since UART does not support DMA, allowing us to interrupt less frequently. const FIFO_SZ: usize = 16; // The HW gives us a convenient scratch register which we use to hold interrupt flags mod int_flag { pub(crate) const RX_AVAILABLE: u8 = 1 << 0; pub(crate) const TIMEOUT: u8 = 1 << 1; pub(crate) const TX_EMPTY: u8 = 1 << 2; } /// UART interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let intid = InterruptType::try_from(T::info().reg.data().int_id().read().intid()); match intid { // In case of RxAvailable, We need a way to know that the FIFO level is triggered in task, // but also need to disable the interrupt to clear it (which clears it in intid). // // In case of LineStatus, We don't use a separate flag from RxAvailable because // the RX task will keep reading bytes until it hits the byte that produced the error Ok(InterruptType::LineStatus) | Ok(InterruptType::RxAvailable) => { T::info().reg.data().scr().modify(|w| *w |= int_flag::RX_AVAILABLE); T::info().reg.data().ien().modify(|w| { w.set_elsi(false); w.set_erdai(false); }); T::info().rx_waker.wake(); } // There does not appear to be a way of disabling this interrupt, and it is always // enabled if RX available interrupt is enabled. The datasheet shows this as equal // priority to RxAvailable, but it seems to have higher priority because even if the // FIFO trigger level is reached, we will always see this interrupt ID until a byte // is read from FIFO. // // This is annoying because we want to be able to batch read to the set RX FIFO // level trigger, but if our input source is slow (such as person typing on keyboard), // this will almost always trigger after the first byte is received (since we want // to wait until the FIFO trigger is reached until reading bytes from FIFO). // // Alas we have to deal with this, which the RX task will do by falling back into // interrupting for every byte. Ok(InterruptType::CharacterTimeout) => { T::info().reg.data().scr().modify(|w| *w |= int_flag::TIMEOUT); T::info().reg.data().ien().modify(|w| { w.set_elsi(false); w.set_erdai(false); }); T::info().rx_waker.wake(); } // Note: We mark TX empty flag because although we could check LSR for tx empty // in the TX task, doing so will clear error bits in LSR. // // This is important because there is a potential race where even though Line Status // interrupt is higher priority, we disable it above so the interrupt could trigger // again, waking the TX task, which might go before the RX task, and it would end up // accidentally clearing LSR error bits before RX task can see them. Ok(InterruptType::TxEmpty) => { T::info().reg.data().scr().modify(|w| *w |= int_flag::TX_EMPTY); T::info().reg.data().ien().modify(|w| w.set_ethrei(false)); T::info().tx_waker.wake(); } // Unknown/unsupported interrupt type, so ignore _ => (), } } } enum InterruptType { LineStatus, RxAvailable, CharacterTimeout, TxEmpty, // MODEM is not currently supported _Modem, } impl TryFrom for InterruptType { type Error = (); fn try_from(value: u8) -> Result { // Note: This always assumes we are in FIFO mode (the driver does not support non-FIFO mode) Ok(match value { 0b011 => Self::LineStatus, 0b010 => Self::RxAvailable, 0b110 => Self::CharacterTimeout, 0b001 => Self::TxEmpty, 0b000 => Self::_Modem, _ => Err(())?, }) } } enum RxFifoTrigger { _1, _4, _8, _14, } impl From for u8 { // Note: These are the bit representations in the register fn from(trigger: RxFifoTrigger) -> Self { match trigger { RxFifoTrigger::_1 => 0b00, RxFifoTrigger::_4 => 0b01, RxFifoTrigger::_8 => 0b10, RxFifoTrigger::_14 => 0b11, } } } /// Data word length. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum WordLen { /// 5 bits. _5, /// 6 bits. _6, /// 7 bits. _7, /// 8 bits. _8, } impl WordLen { fn as_bits(&self) -> u8 { match self { Self::_5 => 0b00, Self::_6 => 0b01, Self::_7 => 0b10, Self::_8 => 0b11, } } } /// Parity selection. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Parity { /// No parity bit. None, /// Odd parity. Odd, /// Even parity. Even, /// Mark parity. Mark, /// Space parity. Space, } impl Parity { fn as_par_sel(&self) -> bool { matches!(*self, Self::Even | Self::Space) } fn as_stick_par(&self) -> bool { matches!(*self, Self::Mark | Self::Space) } } /// Baud clock source. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClkSrc { /// External clock (in Hz). External(u32), /// Internal 1.8432 MHz clock. _1_8432MHz, /// Internal 48 MHz clock. _48MHz, } impl ClkSrc { fn as_bits(&self) -> bool { matches!(self, ClkSrc::External(_)) } fn as_baud_clk_sel_bits(&self) -> u8 { // If using external clock source this is just a "don't care" bit (*self == Self::_48MHz) as u8 } } impl From for u32 { fn from(clk_src: ClkSrc) -> Self { match clk_src { ClkSrc::External(f) => f, ClkSrc::_1_8432MHz => 1_843_200, ClkSrc::_48MHz => 48_000_000, } } } /// UART configuration. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Config { /// The clock source which the baud clock is derived from. /// /// This will affect which baud rates can be accurately represented. pub clk_src: ClkSrc, /// Baudrate in bits per second (BPS). pub baudrate: u32, /// Data word length. /// /// Note: If multi stop bits is enabled, the number of stop bits corresponds to chosen word length. pub word_len: WordLen, /// Enable multiple stop bits. /// /// If enabled, the number of stop bits will be dependent on chosen word length. /// /// If disabled, the number of stop bits will always be 1. pub use_multi_stop: bool, /// Parity selection. pub parity: Parity, /// Enable inverted polarity on RX and TX pins. pub invert_polarity: bool, } impl Default for Config { fn default() -> Self { Self { clk_src: ClkSrc::_1_8432MHz, baudrate: 115_200, word_len: WordLen::_8, use_multi_stop: false, parity: Parity::None, invert_polarity: false, } } } /// UART error. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// A baud rate was supplied which can not be represenetd based on chosen clock source. InvalidBaud, /// RX FIFO overrun occurred. Overrun, /// RX parity error occurred. Parity, /// RX frame error occurred. Frame, /// RX break interrupt occurred. Break, } /// UART RX error. /// /// Contains the [`Error`] along with number of valid bytes read before error occurred. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct RxError { /// Bytes read before error occurred. pub bytes_read: usize, /// The actual error encountered. pub err: Error, } impl core::fmt::Display for RxError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self.err { Error::InvalidBaud => write!( f, "A baud rate was supplied which can not be represenetd based on chosen clock source." ), Error::Overrun => write!(f, "RX FIFO overrun occurred."), Error::Parity => write!(f, "RX parity error occurred."), Error::Frame => write!(f, "RX frame error occurred."), Error::Break => write!(f, "RX break interrupt occurred."), } } } impl core::error::Error for RxError {} fn init<'d, T: Instance>( _rx_pin: Option<&Peri<'d, AnyPin>>, _tx_pin: Option<&Peri<'d, AnyPin>>, config: Config, ) -> Result<(), Error> { // Ensure baudrate is nonzero let divisor = u32::from(config.clk_src) .checked_div(16 * config.baudrate) .ok_or(Error::InvalidBaud)? as u16; // Divisor can be 15 bits max (the 16th bit is used to select clock source) if divisor > 0x7FFF { return Err(Error::InvalidBaud); }; // Configure pins critical_section::with(|_| { if let Some(rx) = _rx_pin { rx.regs().ctrl1.modify(|w| { w.set_mux_ctrl(pac::Function::F1); w.set_dir(pac::Dir::INPUT); w.set_inp_dis(false); w.set_pu_pd(pac::Pull::NONE); }) } if let Some(tx) = _tx_pin { tx.regs().ctrl1.modify(|w| { w.set_mux_ctrl(pac::Function::F1); w.set_dir(pac::Dir::OUTPUT); w.set_inp_dis(true); w.set_pu_pd(pac::Pull::NONE); }) } }); // Set config T::info().reg.data().cfg_sel().write(|w| { w.set_clk_src(config.clk_src.as_bits()); w.set_polar(config.invert_polarity); }); // Set line control with latched DLAB since we modify baud registers next T::info().reg.data().lcr().write(|w| { w.set_word_len(config.word_len.as_bits()); w.set_stop_bits(config.use_multi_stop); w.set_en_par(config.parity != Parity::None); if config.parity != Parity::None { w.set_par_sel(config.parity.as_par_sel()); w.set_stick_par(config.parity.as_stick_par()); } w.set_dlab(true); }); // Set baud rate divisor MSB // Note: bit 7 determines the baud clock source, // but the PAC doesn't appear to have a type with fields for this reg T::info() .reg .dlab() .baudrt_msb() .write(|w| *w = (config.clk_src.as_baud_clk_sel_bits() << 7) | (divisor >> 8) as u8); // Set baud rate divisor LSB T::info().reg.dlab().baudrt_lsb().write(|w| *w = divisor as u8); // Unlatch DLAB, remaining unlatched for rest of instance's lifetime T::info().reg.dlab().lcr().modify(|w| w.set_dlab(false)); // Enable and clear FIFO T::info().reg.data().fifo_cr().write(|w| { w.set_exrf(true); w.set_clr_recv_fifo(true); w.set_clr_xmit_fifo(true); }); // Ensure scratch is cleared which we use for interrupt flags T::info().reg.data().scr().write(|w| *w = 0); // Enable UART T::info().reg.data().activate().write(|w| *w = 1); Ok(()) } fn interrupt_en() { T::info().reg.data().mcr().modify(|w| w.set_out2(true)); // Unmask interrupt pac::ECIA.en_set15().write_value(1 << T::irq_bit()); T::Interrupt::unpend(); // SAFETY: We have sole control of UART interrupts so this is safe to do unsafe { T::Interrupt::enable() }; } fn drop_rx_tx(info: &'static Info) { // Only disable UART once both UartRx and UartTx have been dropped if info.rx_tx_refcount.fetch_sub(1, Ordering::AcqRel) == 1 { info.reg.data().mcr().modify(|w| w.set_out2(false)); info.reg.data().activate().write(|w| *w = 0); } } /// UART driver. pub struct Uart<'d, M: Mode> { rx: UartRx<'d, M>, tx: UartTx<'d, M>, } impl<'d, M: Mode> Uart<'d, M> { fn new_inner( _rx_pin: Peri<'d, impl RxPin>, _tx_pin: Peri<'d, impl TxPin>, config: Config, ) -> Result { let rx_pin = _rx_pin.into(); let tx_pin = _tx_pin.into(); init::(Some(&rx_pin), Some(&tx_pin), config)?; let rx = UartRx::new_inner::(rx_pin); let tx = UartTx::new_inner::(tx_pin); Ok(Self { rx, tx }) } /// Reads bytes from RX FIFO until buffer is full, blocking if empty. /// /// # Errors /// /// Returns [`RxError`] if error occurred during read. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<(), RxError> { self.rx.blocking_read(buf) } /// Writes bytes to TX FIFO, blocking if full. pub fn blocking_write(&mut self, bytes: &[u8]) { self.tx.blocking_write(bytes) } /// Blocks until both the transmit holding and shift registers are empty. pub fn blocking_flush(&mut self) { self.tx.blocking_flush() } /// Splits the UART driver into separate [`UartRx`] and [`UartTx`] drivers. /// /// Helpful for sharing the UART among receiver/transmitter tasks. pub fn split(self) -> (UartRx<'d, M>, UartTx<'d, M>) { (self.rx, self.tx) } /// Splits the UART driver into separate [`UartRx`] and [`UartTx`] drivers by mutable reference. /// /// Helpful for sharing the UART among receiver/transmitter tasks without destroying the original [`Uart`] instance. pub fn split_ref(&mut self) -> (&mut UartRx<'d, M>, &mut UartTx<'d, M>) { (&mut self.rx, &mut self.tx) } } impl<'d> Uart<'d, Blocking> { /// Create a new blocking UART driver instance with given configuration. /// /// # Errors /// /// Returns [`Error::InvalidBaud`] if the supplied baud rate can not be represented by /// given clock source. pub fn new_blocking( _peri: Peri<'d, T>, _rx_pin: Peri<'d, impl RxPin>, _tx_pin: Peri<'d, impl TxPin>, config: Config, ) -> Result { Self::new_inner(_rx_pin, _tx_pin, config) } } impl<'d> Uart<'d, Async> { /// Create a new async UART driver instance with given configuration. /// /// # Errors /// /// Returns [`Error::InvalidBaud`] if the supplied baud rate can not be represented by /// given clock source. pub fn new_async( _peri: Peri<'d, T>, _rx_pin: Peri<'d, impl RxPin>, _tx_pin: Peri<'d, impl TxPin>, _irq: impl Binding>, config: Config, ) -> Result { let uart = Self::new_inner(_rx_pin, _tx_pin, config)?; interrupt_en::(); Ok(uart) } /// Reads bytes from RX FIFO until buffer is full. /// /// # Errors /// /// Returns [`RxError`] if error occurred during read. pub fn read(&mut self, buf: &mut [u8]) -> impl Future> { self.rx.read(buf) } /// Writes bytes to TX FIFO. pub fn write(&mut self, bytes: &[u8]) -> impl Future { self.tx.write(bytes) } /// Waits until both the transmit holding and shift registers are empty. pub fn flush(&mut self) -> impl Future { self.tx.flush() } } /// RX-only UART driver. pub struct UartRx<'d, M: Mode> { info: &'static Info, _rx_pin: Peri<'d, AnyPin>, _phantom: PhantomData<&'d M>, } impl<'d, M: Mode> UartRx<'d, M> { fn new_inner(_rx_pin: Peri<'d, AnyPin>) -> Self { T::info().rx_tx_refcount.fetch_add(1, Ordering::AcqRel); Self { info: T::info(), _rx_pin, _phantom: PhantomData, } } fn read_inner(&mut self, lsr: DataLsr) -> Result { // An overrun error is not associated with particular character like others // We just bail out early, and it might be hard to recover correctly from this // // The onus will likely need to be on caller to handle an overrun how they see fit if lsr.overrun() { return Err(Error::Overrun); } // Read byte first (because even if the byte produces an error we still want to drain it) let byte = self.info.reg.data().rx_dat().read(); // Note: We don't make use of bit 7 (FIFO_ERROR) because it just tells us if one of the // below errors is somewhere in the FIFO, but we are checking the specific byte at top of // the FIFO for an error // And only return it if there is no error if lsr.pe() { Err(Error::Parity) } else if lsr.frame_err() { Err(Error::Frame) } else if lsr.brk_intr() { Err(Error::Break) } else { Ok(byte) } } fn nb_read_byte(&mut self) -> Result { let lsr = self.info.reg.data().lsr().read(); self.read_inner(lsr) } fn blocking_read_byte(&mut self) -> Result { // LSR clears error bits on read so want to make sure only read it once after data ready let lsr = loop { let lsr = self.info.reg.data().lsr().read(); if lsr.data_ready() { break lsr; } }; self.read_inner(lsr) } /// Reads bytes from RX FIFO until buffer is full, blocking if empty. /// /// # Errors /// /// Returns [`RxError`] if error occurred during read. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<(), RxError> { // If we encounter an error, return the number of valid bytes read up until error occurred for (bytes_read, byte) in buf.iter_mut().enumerate() { *byte = self.blocking_read_byte().map_err(|err| RxError { bytes_read, err })?; } Ok(()) } } impl<'d> UartRx<'d, Blocking> { /// Create a new blocking RX-only UART driver instance with given configuration. /// /// # Errors /// /// Returns [`Error::InvalidBaud`] if the supplied baud rate can not be represented by /// given clock source. pub fn new_blocking( _peri: Peri<'d, T>, _rx_pin: Peri<'d, impl RxPin>, config: Config, ) -> Result { let rx_pin = _rx_pin.into(); init::(Some(&rx_pin), None, config)?; Ok(Self::new_inner::(rx_pin)) } } impl<'d> UartRx<'d, Async> { async fn wait_rx_ready(&mut self) -> bool { poll_fn(|cx| { self.info.rx_waker.register(cx.waker()); let int_flags = self.info.reg.data().scr().read(); if int_flags & int_flag::TIMEOUT != 0 { critical_section::with(|_| self.info.reg.data().scr().modify(|w| *w &= !int_flag::TIMEOUT)); // Indicates a byte is ready to read, but we didn't trigger the FIFO level before timeout Poll::Ready(false) } else if int_flags & int_flag::RX_AVAILABLE != 0 { critical_section::with(|_| self.info.reg.data().scr().modify(|w| *w &= !int_flag::RX_AVAILABLE)); Poll::Ready(true) } else { critical_section::with(|_| { self.info.reg.data().ien().modify(|w| { w.set_elsi(true); w.set_erdai(true); }) }); Poll::Pending } }) .await } fn set_fifo_trigger(&mut self, trigger: RxFifoTrigger) { self.info.reg.data().fifo_cr().write(|w| { // Must always set EXRF when setting other bits in the reg, even if previously set w.set_exrf(true); w.set_recv_fifo_trig_lvl(trigger.into()); }); } async fn read_byte(&mut self) -> Result { // LSR clears error bits on read so want to make sure only read it once when data ready then check bits let lsr = { let lsr = self.info.reg.data().lsr().read(); if !lsr.data_ready() { let _ = self.wait_rx_ready().await; self.info.reg.data().lsr().read() } else { lsr } }; self.read_inner(lsr) } async fn read_chunk(&mut self, chunk: &mut [u8], bytes_read_start: usize) -> Result<(), RxError> { self.set_fifo_trigger(RxFifoTrigger::_1); for (bytes_read, byte) in chunk.iter_mut().enumerate() { *byte = self.read_byte().await.map_err(|err| RxError { bytes_read: bytes_read_start + bytes_read, err, })?; } Ok(()) } async fn read_chunk_batched(&mut self, chunk: &mut [u8], bytes_read_start: usize) -> Result<(), RxError> { // If our FIFO level was reached without timeout, we can read all the bytes in one go, // but still need to check each byte for an error if self.wait_rx_ready().await { for (bytes_read, byte) in chunk.iter_mut().enumerate() { *byte = self.nb_read_byte().map_err(|err| RxError { bytes_read: bytes_read_start + bytes_read, err, })?; } // However, if a timeout occured, our assumptions about the number of bytes in the FIFO // no longer holds (since we have to read a byte to clear the timeout interrupt), meaning // we have no choice but to unfortunately fall back on byte-by-byte interrupts } else { self.read_chunk(chunk, bytes_read_start).await?; } Ok(()) } async fn read_chunks( &mut self, chunks: &mut [[u8; N]], trigger: RxFifoTrigger, bytes_read_start: usize, ) -> Result<(), RxError> { self.set_fifo_trigger(trigger); for (i, chunk) in chunks.iter_mut().enumerate() { self.read_chunk_batched(chunk, bytes_read_start + (N * i)).await?; } Ok(()) } /// Create a new async RX-only UART driver instance with given configuration. /// /// # Errors /// /// Returns [`Error::InvalidBaud`] if the supplied baud rate can not be represented by /// given clock source. pub fn new_async( _peri: Peri<'d, T>, _rx_pin: Peri<'d, impl RxPin>, _irq: impl Binding>, config: Config, ) -> Result { let rx_pin = _rx_pin.into(); init::(Some(&rx_pin), None, config)?; interrupt_en::(); Ok(Self::new_inner::(rx_pin)) } /// Reads bytes from RX FIFO until buffer is full. /// /// # Errors /// /// Returns [`RxError`] if error occurred during read. pub async fn read(&mut self, buf: &mut [u8]) -> Result<(), RxError> { // The idea here is that the HW provides us 4 FIFO level triggers (14, 8, 4, 1), // so to minimize the number of interrupts, we split the buffer up greedily into chunks // of the highest trigger level we can, and then know when the interrupt is triggered, // we can read that number of bytes in one shot. const TRIG_14: usize = 14; const TRIG_8: usize = 8; const TRIG_4: usize = 4; let (c14, rem) = buf.as_chunks_mut::(); let (c8, rem) = rem.as_chunks_mut::(); let (c4, c1) = rem.as_chunks_mut::(); // We keep track of total number of valid bytes read, so in case of error, // we can return this number to the caller let mut bytes_read = 0; self.read_chunks(c14, RxFifoTrigger::_14, bytes_read).await?; bytes_read += c14.len() * TRIG_14; self.read_chunks(c8, RxFifoTrigger::_8, bytes_read).await?; bytes_read += c8.len() * TRIG_8; self.read_chunks(c4, RxFifoTrigger::_4, bytes_read).await?; bytes_read += c4.len() * TRIG_4; // The last chunk is smaller than 4, so we have no choice but to interrupt for each byte self.read_chunk(c1, bytes_read).await?; Ok(()) } } impl<'d, M: Mode> Drop for UartRx<'d, M> { fn drop(&mut self) { // Revisit: Add API for disabling pins drop_rx_tx(self.info); } } /// TX-only UART driver. pub struct UartTx<'d, M: Mode> { info: &'static Info, _tx_pin: Peri<'d, AnyPin>, _phantom: PhantomData<&'d M>, } impl<'d, M: Mode> UartTx<'d, M> { fn new_inner(_tx_pin: Peri<'d, AnyPin>) -> Self { T::info().rx_tx_refcount.fetch_add(1, Ordering::AcqRel); Self { info: T::info(), _tx_pin, _phantom: PhantomData, } } fn write_chunk(&mut self, chunk: &[u8]) { for byte in chunk { self.info.reg.data().tx_dat().write(|w| *w = *byte); } } /// Writes bytes to TX FIFO, blocking if full. pub fn blocking_write(&mut self, buf: &[u8]) { for chunk in buf.chunks(FIFO_SZ) { while !self.info.reg.data().lsr().read().trans_empty() {} self.write_chunk(chunk); } } /// Blocks until both the transmit holding and shift registers are empty. pub fn blocking_flush(&mut self) { // Note: The register for some reason is named "Transmit Error" but it really // reflects the status of both the transmit and shift registers aka "Busy" status while !self.info.reg.data().lsr().read().trans_err() {} } } impl<'d> UartTx<'d, Blocking> { /// Create a new blocking TX-only UART driver instance with given configuration. /// /// # Errors /// /// Returns [`Error::InvalidBaud`] if the supplied baud rate can not be represented by /// given clock source. pub fn new_blocking( _peri: Peri<'d, T>, _tx_pin: Peri<'d, impl TxPin>, config: Config, ) -> Result { let tx_pin = _tx_pin.into(); init::(None, Some(&tx_pin), config)?; Ok(Self::new_inner::(tx_pin)) } } impl<'d> UartTx<'d, Async> { async fn wait_tx_empty(&mut self) { poll_fn(|cx| { self.info.tx_waker.register(cx.waker()); if self.info.reg.data().scr().read() & int_flag::TX_EMPTY != 0 { critical_section::with(|_| self.info.reg.data().scr().modify(|w| *w &= !int_flag::TX_EMPTY)); Poll::Ready(()) } else { critical_section::with(|_| self.info.reg.data().ien().modify(|w| w.set_ethrei(true))); Poll::Pending } }) .await } /// Create a new async TX-only UART driver instance with given configuration. /// /// # Errors /// /// Returns [`Error::InvalidBaud`] if the supplied baud rate can not be represented by /// given clock source. pub fn new_async( _peri: Peri<'d, T>, _tx_pin: Peri<'d, impl TxPin>, _irq: impl Binding>, config: Config, ) -> Result { let tx_pin = _tx_pin.into(); init::(None, Some(&tx_pin), config)?; interrupt_en::(); Ok(Self::new_inner::(tx_pin)) } /// Writes bytes to TX FIFO. pub async fn write(&mut self, buf: &[u8]) { for chunk in buf.chunks(FIFO_SZ) { self.wait_tx_empty().await; self.write_chunk(chunk); } } /// Waits until both the transmit holding and shift registers are empty. pub async fn flush(&mut self) { // We can wait for an interrupt to know when the TX FIFO is empty, // but there does not appear to be an interrupt for when the TX shift reg is empty, // so have to block self.wait_tx_empty().await; self.blocking_flush(); } } impl<'d, M: Mode> Drop for UartTx<'d, M> { fn drop(&mut self) { // Revisit: Add API for disabling pins drop_rx_tx(self.info); } } struct Info { reg: Uart0, rx_tx_refcount: AtomicU8, rx_waker: AtomicWaker, tx_waker: AtomicWaker, } trait SealedMode {} /// Blocking mode. pub struct Blocking; impl SealedMode for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl SealedMode for Async {} impl Mode for Async {} /// Driver mode. #[allow(private_bounds)] pub trait Mode: SealedMode {} trait SealedInstance { fn info() -> &'static Info; fn irq_bit() -> usize; } /// UART instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_instance { ($peri:ident, $bit:expr) => { impl SealedInstance for peripherals::$peri { #[inline(always)] fn info() -> &'static Info { static INFO: Info = Info { reg: pac::$peri, rx_tx_refcount: AtomicU8::new(0), rx_waker: AtomicWaker::new(), tx_waker: AtomicWaker::new(), }; &INFO } #[inline(always)] fn irq_bit() -> usize { $bit } } impl Instance for peripherals::$peri { type Interrupt = crate::interrupt::typelevel::$peri; } }; } impl_instance!(UART0, 0); impl_instance!(UART1, 1); /// A pin that can be configured as a UART RX pin. pub trait RxPin: Pin + PeripheralType {} /// A pin that can be configured as a UART TX pin. pub trait TxPin: Pin + PeripheralType {} macro_rules! impl_pin { ($function:ident, $peri:ident, $($pin:ident),*) => { $( impl $function for peripherals::$pin {} )* } } impl_pin!(RxPin, UART0, GPIO105); impl_pin!(TxPin, UART0, GPIO104); impl_pin!(RxPin, UART1, GPIO171, GPIO255); impl_pin!(TxPin, UART1, GPIO170); impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, M> { type Error = core::convert::Infallible; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { self.blocking_write(buffer); Ok(()) } fn bflush(&mut self) -> Result<(), Self::Error> { self.blocking_flush(); Ok(()) } } impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, M> { type Error = core::convert::Infallible; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { self.blocking_write(buffer); Ok(()) } fn bflush(&mut self) -> Result<(), Self::Error> { self.blocking_flush(); Ok(()) } } impl embedded_io::Error for RxError { fn kind(&self) -> embedded_io::ErrorKind { // Overrun error may need to be handled differently than other errors, // but there isn't a perfect `ErrorKind` match for it if self.err == Error::Overrun { embedded_io::ErrorKind::Other } else { embedded_io::ErrorKind::InvalidData } } } impl<'d, M: Mode> embedded_io::ErrorType for Uart<'d, M> { type Error = RxError; } impl<'d, M: Mode> embedded_io::Read for Uart<'d, M> { fn read(&mut self, buf: &mut [u8]) -> Result { self.blocking_read(buf).map(|_| buf.len()) } } impl<'d> embedded_io_async::Read for Uart<'d, Async> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).await.map(|_| buf.len()) } } impl<'d, M: Mode> embedded_io::Write for Uart<'d, 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(); Ok(()) } } impl<'d> embedded_io_async::Write for Uart<'d, 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; Ok(()) } } impl<'d, M: Mode> embedded_io::ErrorType for UartTx<'d, M> { type Error = core::convert::Infallible; } impl<'d, M: Mode> embedded_io::Write for UartTx<'d, 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(); Ok(()) } } impl<'d> embedded_io_async::Write for UartTx<'d, 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; Ok(()) } } impl<'d, M: Mode> embedded_io::ErrorType for UartRx<'d, M> { type Error = RxError; } impl<'d, M: Mode> embedded_io::Read for UartRx<'d, M> { fn read(&mut self, buf: &mut [u8]) -> Result { self.blocking_read(buf).map(|_| buf.len()) } } impl<'d> embedded_io_async::Read for UartRx<'d, Async> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).await.map(|_| buf.len()) } } ================================================ FILE: embassy-mspm0/CHANGELOG.md ================================================ # Changelog for embassy-mspm0 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 - ReleaseDate - feat: Add I2C Controller (blocking & async) + examples for mspm0l1306, mspm0g3507 (tested MCUs) (#4435) - fix gpio interrupt not being set for mspm0l110x - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) - feat: Add MSPM0C1105/C1106 support - feat: Add adc implementation (#4646) - fix: gpio OutputOpenDrain config (#4735) - fix: add MSPM0C1106 to build test matrix - feat: add MSPM0H3216 support - feat: Add i2c target implementation (#4605) - fix: group irq handlers must check for NO_INTR (#4785) - feat: Add read_reset_cause function - feat: Add module Mathacl & example for mspm0g3507 (#4897) - feat: Add MSPM0G5187 support - feat: add CPU accelerated division function (#4966) - feat: Add trng implementation (#5172) - fix: feature guard pins used for NRST and SWD (#5257) ================================================ FILE: embassy-mspm0/Cargo.toml ================================================ [package] name = "embassy-mspm0" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for Texas Instruments MSPM0 series microcontrollers" keywords = ["embedded", "async", "mspm0", "hal", "embedded-hal"] categories = ["embedded", "hardware-support", "no-std", "asynchronous"] repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-mspm0" publish = false [package.metadata.embassy] build = [ {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0c1104dgs20", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0c1106rgz", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1107ycj", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1505pt", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1519rhb", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3105rhb", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3507pm", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3519pz", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g5187pm", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0h3216pt", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l2228pn", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1345dgs28", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1106dgs28", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1228pt", "time-driver-any"]}, ] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-mspm0-v$VERSION/embassy-mspm0/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-mspm0/src/" features = ["defmt", "unstable-pac", "time-driver-any"] flavors = [ { regex_feature = "mspm0c.*", target = "thumbv6m-none-eabi" }, { regex_feature = "mspm0l.*", target = "thumbv6m-none-eabi" }, { regex_feature = "mspm0g.*", target = "thumbv6m-none-eabi" }, { regex_feature = "mspm0h.*", target = "thumbv6m-none-eabi" }, ] [package.metadata.docs.rs] features = ["defmt", "unstable-pac", "time-driver-any", "time", "mspm0g3507"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] embassy-sync = { version = "0.8.0", path = "../embassy-sync" } # TODO: Support other tick rates embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", optional = true, features = ["tick-hz-32_768"] } embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-utils", optional = true } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-hal-internal = { version = "0.5.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal", default-features = false } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal = { version = "1.0" } embedded-hal-nb = { version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-io = { version = "0.7.1" } embedded-io-async = { version = "0.7.0" } defmt = { version = "1.0.1", optional = true } fixed = "1.29" log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" critical-section = "1.2.0" micromath = "2.0.0" # mspm0-metapac = { version = "" } mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-d9f54366c65cec47957065f42fe04542b852a8cd" } rand_core = "0.9" [build-dependencies] proc-macro2 = "1.0.94" quote = "1.0.40" cfg_aliases = "0.2.1" # mspm0-metapac = { version = "", default-features = false, features = ["metadata"] } mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-d9f54366c65cec47957065f42fe04542b852a8cd", default-features = false, features = ["metadata"] } [features] default = ["rt"] ## Enable `mspm0-metapac`'s `rt` feature rt = ["mspm0-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", ] ## Use log for logging log = ["dep:log"] ## Re-export mspm0-metapac at `mspm0::pac`. ## This is unstable because semver-minor (non-breaking) releases of embassy-mspm0 may major-bump (breaking) the mspm0-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 = [] ## Allow using the NRST pin as a regular GPIO pin. ## This is not applicable to all chips. In particular MSPM0C1103/4 share PA1 with NRST. nrst-pin-as-gpio = [] ## Allow using the SWD pins as regular GPIO pins. swd-pins-as-gpio = [] #! ## 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", "dep:embassy-time-queue-utils"] # Use any time driver time-driver-any = ["_time-driver"] ## Use TIMG0 as time driver time-driver-timg0 = ["_time-driver"] ## Use TIMG1 as time driver time-driver-timg1 = ["_time-driver"] ## Use TIMG2 as time driver time-driver-timg2 = ["_time-driver"] ## Use TIMG3 as time driver time-driver-timg3 = ["_time-driver"] ## Use TIMG4 as time driver time-driver-timg4 = ["_time-driver"] ## Use TIMG5 as time driver time-driver-timg5 = ["_time-driver"] ## Use TIMG6 as time driver time-driver-timg6 = ["_time-driver"] ## Use TIMG7 as time driver time-driver-timg7 = ["_time-driver"] ## Use TIMG8 as time driver time-driver-timg8 = ["_time-driver"] ## Use TIMG9 as time driver time-driver-timg9 = ["_time-driver"] ## Use TIMG10 as time driver time-driver-timg10 = ["_time-driver"] ## Use TIMG11 as time driver time-driver-timg11 = ["_time-driver"] ## Use TIMG12 as time driver time-driver-timg12 = ["_time-driver"] ## Use TIMG13 as time driver time-driver-timg13 = ["_time-driver"] ## Use TIMG14 as time driver time-driver-timg14 = ["_time-driver"] ## Use TIMA0 as time driver time-driver-tima0 = ["_time-driver"] ## Use TIMA1 as time driver time-driver-tima1 = ["_time-driver"] #! ## Chip-selection features #! Select your chip by specifying the model as a feature, e.g. `mspm0g3507pm`. #! 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`. mspm0c1103dgs20 = ["mspm0-metapac/mspm0c1103dgs20"] mspm0c1103ruk = ["mspm0-metapac/mspm0c1103ruk"] mspm0c1103dyy = ["mspm0-metapac/mspm0c1103dyy"] mspm0c1103dsg = ["mspm0-metapac/mspm0c1103dsg"] mspm0c1104dgs20 = ["mspm0-metapac/mspm0c1104dgs20"] mspm0c1104ruk = ["mspm0-metapac/mspm0c1104ruk"] mspm0c1104dyy = ["mspm0-metapac/mspm0c1104dyy"] mspm0c1104dsg = ["mspm0-metapac/mspm0c1104dsg"] mspm0c1104ycj = ["mspm0-metapac/mspm0c1104ycj"] mspm0c1105pt = ["mspm0-metapac/mspm0c1105pt"] mspm0c1105rgz = ["mspm0-metapac/mspm0c1105rgz"] mspm0c1105rhb = ["mspm0-metapac/mspm0c1105rhb"] mspm0c1105dgs32 = ["mspm0-metapac/mspm0c1105dgs32"] mspm0c1105dgs28 = ["mspm0-metapac/mspm0c1105dgs28"] mspm0c1105rge = ["mspm0-metapac/mspm0c1105rge"] mspm0c1105dgs20 = ["mspm0-metapac/mspm0c1105dgs20"] mspm0c1105ruk = ["mspm0-metapac/mspm0c1105ruk"] mspm0c1105zcm = ["mspm0-metapac/mspm0c1105zcm"] mspm0c1106pt = ["mspm0-metapac/mspm0c1106pt"] mspm0c1106rgz = ["mspm0-metapac/mspm0c1106rgz"] mspm0c1106rhb = ["mspm0-metapac/mspm0c1106rhb"] mspm0c1106dgs32 = ["mspm0-metapac/mspm0c1106dgs32"] mspm0c1106dgs28 = ["mspm0-metapac/mspm0c1106dgs28"] mspm0c1106rge = ["mspm0-metapac/mspm0c1106rge"] mspm0c1106dgs20 = ["mspm0-metapac/mspm0c1106dgs20"] mspm0c1106ruk = ["mspm0-metapac/mspm0c1106ruk"] mspm0c1106zcm = ["mspm0-metapac/mspm0c1106zcm"] mspm0g1105rge = ["mspm0-metapac/mspm0g1105rge"] mspm0g1105dgs28 = ["mspm0-metapac/mspm0g1105dgs28"] mspm0g1105rhb = ["mspm0-metapac/mspm0g1105rhb"] mspm0g1105rgz = ["mspm0-metapac/mspm0g1105rgz"] mspm0g1105pt = ["mspm0-metapac/mspm0g1105pt"] mspm0g1105pm = ["mspm0-metapac/mspm0g1105pm"] mspm0g1106rge = ["mspm0-metapac/mspm0g1106rge"] mspm0g1106dgs28 = ["mspm0-metapac/mspm0g1106dgs28"] mspm0g1106rhb = ["mspm0-metapac/mspm0g1106rhb"] mspm0g1106rgz = ["mspm0-metapac/mspm0g1106rgz"] mspm0g1106pt = ["mspm0-metapac/mspm0g1106pt"] mspm0g1106pm = ["mspm0-metapac/mspm0g1106pm"] mspm0g1107rge = ["mspm0-metapac/mspm0g1107rge"] mspm0g1107dgs28 = ["mspm0-metapac/mspm0g1107dgs28"] mspm0g1107rhb = ["mspm0-metapac/mspm0g1107rhb"] mspm0g1107rgz = ["mspm0-metapac/mspm0g1107rgz"] mspm0g1107pt = ["mspm0-metapac/mspm0g1107pt"] mspm0g1107pm = ["mspm0-metapac/mspm0g1107pm"] mspm0g1107ycj = ["mspm0-metapac/mspm0g1107ycj"] mspm0g1505rge = ["mspm0-metapac/mspm0g1505rge"] mspm0g1505rhb = ["mspm0-metapac/mspm0g1505rhb"] mspm0g1505rgz = ["mspm0-metapac/mspm0g1505rgz"] mspm0g1505pt = ["mspm0-metapac/mspm0g1505pt"] mspm0g1505pm = ["mspm0-metapac/mspm0g1505pm"] mspm0g1506rge = ["mspm0-metapac/mspm0g1506rge"] mspm0g1506rhb = ["mspm0-metapac/mspm0g1506rhb"] mspm0g1506rgz = ["mspm0-metapac/mspm0g1506rgz"] mspm0g1506pt = ["mspm0-metapac/mspm0g1506pt"] mspm0g1506pm = ["mspm0-metapac/mspm0g1506pm"] mspm0g1507rge = ["mspm0-metapac/mspm0g1507rge"] mspm0g1507rhb = ["mspm0-metapac/mspm0g1507rhb"] mspm0g1507rgz = ["mspm0-metapac/mspm0g1507rgz"] mspm0g1507pt = ["mspm0-metapac/mspm0g1507pt"] mspm0g1507pm = ["mspm0-metapac/mspm0g1507pm"] mspm0g1507ycj = ["mspm0-metapac/mspm0g1507ycj"] mspm0g1518rhb = ["mspm0-metapac/mspm0g1518rhb"] mspm0g1518rgz = ["mspm0-metapac/mspm0g1518rgz"] mspm0g1518pt = ["mspm0-metapac/mspm0g1518pt"] mspm0g1518pm = ["mspm0-metapac/mspm0g1518pm"] mspm0g1518pz = ["mspm0-metapac/mspm0g1518pz"] mspm0g1518pn = ["mspm0-metapac/mspm0g1518pn"] mspm0g1518zaw = ["mspm0-metapac/mspm0g1518zaw"] mspm0g1519rhb = ["mspm0-metapac/mspm0g1519rhb"] mspm0g1519rgz = ["mspm0-metapac/mspm0g1519rgz"] mspm0g1519pt = ["mspm0-metapac/mspm0g1519pt"] mspm0g1519pm = ["mspm0-metapac/mspm0g1519pm"] mspm0g1519pz = ["mspm0-metapac/mspm0g1519pz"] mspm0g1519pn = ["mspm0-metapac/mspm0g1519pn"] mspm0g3105dgs20 = ["mspm0-metapac/mspm0g3105dgs20"] mspm0g3105dgs28 = ["mspm0-metapac/mspm0g3105dgs28"] mspm0g3105rhb = ["mspm0-metapac/mspm0g3105rhb"] mspm0g3106dgs20 = ["mspm0-metapac/mspm0g3106dgs20"] mspm0g3106dgs28 = ["mspm0-metapac/mspm0g3106dgs28"] mspm0g3106rhb = ["mspm0-metapac/mspm0g3106rhb"] mspm0g3107dgs20 = ["mspm0-metapac/mspm0g3107dgs20"] mspm0g3107dgs28 = ["mspm0-metapac/mspm0g3107dgs28"] mspm0g3107rhb = ["mspm0-metapac/mspm0g3107rhb"] mspm0g3505dgs28 = ["mspm0-metapac/mspm0g3505dgs28"] mspm0g3505rhb = ["mspm0-metapac/mspm0g3505rhb"] mspm0g3505rgz = ["mspm0-metapac/mspm0g3505rgz"] mspm0g3505pt = ["mspm0-metapac/mspm0g3505pt"] mspm0g3505pm = ["mspm0-metapac/mspm0g3505pm"] mspm0g3506dgs28 = ["mspm0-metapac/mspm0g3506dgs28"] mspm0g3506rhb = ["mspm0-metapac/mspm0g3506rhb"] mspm0g3506rgz = ["mspm0-metapac/mspm0g3506rgz"] mspm0g3506pt = ["mspm0-metapac/mspm0g3506pt"] mspm0g3506pm = ["mspm0-metapac/mspm0g3506pm"] mspm0g3507dgs28 = ["mspm0-metapac/mspm0g3507dgs28"] mspm0g3507rhb = ["mspm0-metapac/mspm0g3507rhb"] mspm0g3507rgz = ["mspm0-metapac/mspm0g3507rgz"] mspm0g3507pt = ["mspm0-metapac/mspm0g3507pt"] mspm0g3507pm = ["mspm0-metapac/mspm0g3507pm"] mspm0g3518rhb = ["mspm0-metapac/mspm0g3518rhb"] mspm0g3518rgz = ["mspm0-metapac/mspm0g3518rgz"] mspm0g3518pt = ["mspm0-metapac/mspm0g3518pt"] mspm0g3518pm = ["mspm0-metapac/mspm0g3518pm"] mspm0g3518pz = ["mspm0-metapac/mspm0g3518pz"] mspm0g3518pn = ["mspm0-metapac/mspm0g3518pn"] mspm0g3519rhb = ["mspm0-metapac/mspm0g3519rhb"] mspm0g3519rgz = ["mspm0-metapac/mspm0g3519rgz"] mspm0g3519pt = ["mspm0-metapac/mspm0g3519pt"] mspm0g3519pm = ["mspm0-metapac/mspm0g3519pm"] mspm0g3519pz = ["mspm0-metapac/mspm0g3519pz"] mspm0g3519pn = ["mspm0-metapac/mspm0g3519pn"] mspm0g3519zaw = ["mspm0-metapac/mspm0g3519zaw"] mspm0g5187rhb = ["mspm0-metapac/mspm0g5187rhb"] mspm0g5187rgz = ["mspm0-metapac/mspm0g5187rgz"] mspm0g5187pt = ["mspm0-metapac/mspm0g5187pt"] mspm0g5187pm = ["mspm0-metapac/mspm0g5187pm"] mspm0g5187ruy = ["mspm0-metapac/mspm0g5187ruy"] mspm0g5187ycj = ["mspm0-metapac/mspm0g5187ycj"] mspm0g5187rge = ["mspm0-metapac/mspm0g5187rge"] mspm0h3216pt = ["mspm0-metapac/mspm0h3216pt"] mspm0l1105dgs20 = ["mspm0-metapac/mspm0l1105dgs20"] mspm0l1105dgs28 = ["mspm0-metapac/mspm0l1105dgs28"] mspm0l1105rge = ["mspm0-metapac/mspm0l1105rge"] mspm0l1105rtr = ["mspm0-metapac/mspm0l1105rtr"] mspm0l1105dyy = ["mspm0-metapac/mspm0l1105dyy"] mspm0l1106rhb = ["mspm0-metapac/mspm0l1106rhb"] mspm0l1106dgs20 = ["mspm0-metapac/mspm0l1106dgs20"] mspm0l1106dgs28 = ["mspm0-metapac/mspm0l1106dgs28"] mspm0l1106rge = ["mspm0-metapac/mspm0l1106rge"] mspm0l1106rtr = ["mspm0-metapac/mspm0l1106rtr"] mspm0l1106dyy = ["mspm0-metapac/mspm0l1106dyy"] mspm0l1227rhb = ["mspm0-metapac/mspm0l1227rhb"] mspm0l1227pn = ["mspm0-metapac/mspm0l1227pn"] mspm0l1227rgz = ["mspm0-metapac/mspm0l1227rgz"] mspm0l1227pt = ["mspm0-metapac/mspm0l1227pt"] mspm0l1227pm = ["mspm0-metapac/mspm0l1227pm"] mspm0l1227rge = ["mspm0-metapac/mspm0l1227rge"] mspm0l1228rhb = ["mspm0-metapac/mspm0l1228rhb"] mspm0l1228pn = ["mspm0-metapac/mspm0l1228pn"] mspm0l1228rgz = ["mspm0-metapac/mspm0l1228rgz"] mspm0l1228pt = ["mspm0-metapac/mspm0l1228pt"] mspm0l1228pm = ["mspm0-metapac/mspm0l1228pm"] mspm0l1228rge = ["mspm0-metapac/mspm0l1228rge"] mspm0l1303rge = ["mspm0-metapac/mspm0l1303rge"] mspm0l1304rhb = ["mspm0-metapac/mspm0l1304rhb"] mspm0l1304dgs20 = ["mspm0-metapac/mspm0l1304dgs20"] mspm0l1304dgs28 = ["mspm0-metapac/mspm0l1304dgs28"] mspm0l1304rge = ["mspm0-metapac/mspm0l1304rge"] mspm0l1304rtr = ["mspm0-metapac/mspm0l1304rtr"] mspm0l1304dyy = ["mspm0-metapac/mspm0l1304dyy"] mspm0l1305dgs20 = ["mspm0-metapac/mspm0l1305dgs20"] mspm0l1305dgs28 = ["mspm0-metapac/mspm0l1305dgs28"] mspm0l1305rge = ["mspm0-metapac/mspm0l1305rge"] mspm0l1305rtr = ["mspm0-metapac/mspm0l1305rtr"] mspm0l1305dyy = ["mspm0-metapac/mspm0l1305dyy"] mspm0l1306rhb = ["mspm0-metapac/mspm0l1306rhb"] mspm0l1306dgs20 = ["mspm0-metapac/mspm0l1306dgs20"] mspm0l1306dgs28 = ["mspm0-metapac/mspm0l1306dgs28"] mspm0l1306rge = ["mspm0-metapac/mspm0l1306rge"] mspm0l1306dyy = ["mspm0-metapac/mspm0l1306dyy"] mspm0l1343dgs20 = ["mspm0-metapac/mspm0l1343dgs20"] mspm0l1344dgs20 = ["mspm0-metapac/mspm0l1344dgs20"] mspm0l1345dgs28 = ["mspm0-metapac/mspm0l1345dgs28"] mspm0l1346dgs28 = ["mspm0-metapac/mspm0l1346dgs28"] mspm0l2227rgz = ["mspm0-metapac/mspm0l2227rgz"] mspm0l2227pt = ["mspm0-metapac/mspm0l2227pt"] mspm0l2227pm = ["mspm0-metapac/mspm0l2227pm"] mspm0l2227pn = ["mspm0-metapac/mspm0l2227pn"] mspm0l2228rgz = ["mspm0-metapac/mspm0l2228rgz"] mspm0l2228pt = ["mspm0-metapac/mspm0l2228pt"] mspm0l2228pm = ["mspm0-metapac/mspm0l2228pm"] mspm0l2228pn = ["mspm0-metapac/mspm0l2228pn"] msps003f3pw20 = ["mspm0-metapac/msps003f3pw20"] msps003f4pw20 = ["mspm0-metapac/msps003f4pw20"] ================================================ FILE: embassy-mspm0/README.md ================================================ # Embassy MSPM0 HAL The embassy-mspm0 HAL aims to provide a safe, idiomatic hardware abstraction layer for all MSPM0 and MSPS003 chips. * [Documentation](https://docs.embassy.dev/embassy-mspm0/) (**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-mspm0) * [Examples](https://github.com/embassy-rs/embassy/tree/main/examples) ## Embedded-hal The `embassy-mspm0` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (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). ## A note on feature flag names Feature flag names for chips do not include temperature rating or distribution format. Usually chapter 10 of your device's datasheet will explain the device nomenclature and how to decode it. Feature names in embassy-mspm0 only use the following from device nomenclature: - MCU platform - Product family - Device subfamily - Flash memory - Package type This means for a part such as `MSPM0G3507SPMR`, the feature name is `mspm0g3507pm`. This also means that `MSPM0G3507QPMRQ1` uses the feature `mspm0g3507pm`, since the Q1 parts are just qualified variants of the base G3507 with a PM (QFP-64) package. ## Interoperability This crate can run on any executor. ================================================ FILE: embassy-mspm0/build.rs ================================================ use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Write; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::LazyLock; use std::{env, fs}; use common::CfgSet; use mspm0_metapac::metadata::{ALL_CHIPS, METADATA}; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{format_ident, quote}; #[path = "./build_common.rs"] mod common; fn main() { let mut cfgs = common::CfgSet::new(); common::set_target_cfgs(&mut cfgs); generate_code(&mut cfgs); select_gpio_features(&mut cfgs); interrupt_group_linker_magic(); } fn generate_code(cfgs: &mut CfgSet) { #[cfg(any(feature = "rt"))] println!( "cargo:rustc-link-search={}", PathBuf::from(env::var_os("OUT_DIR").unwrap()).display(), ); cfgs.declare_all(&["gpio_pb", "gpio_pc", "int_group1", "unicomm"]); let chip_name = match env::vars() .map(|(a, _)| a) .filter(|x| x.starts_with("CARGO_FEATURE_MSPM0") || x.starts_with("CARGO_FEATURE_MSPS")) .get_one() { Ok(x) => x, Err(GetOneError::None) => panic!("No mspm0xx/mspsxx Cargo feature enabled"), Err(GetOneError::Multiple) => panic!("Multiple mspm0xx/mspsxx Cargo features enabled"), } .strip_prefix("CARGO_FEATURE_") .unwrap() .to_ascii_lowercase() .replace('_', "-"); eprintln!("chip: {chip_name}"); cfgs.enable_all(&get_chip_cfgs(&chip_name)); for chip in ALL_CHIPS { cfgs.declare_all(&get_chip_cfgs(&chip)); } let mut singletons = get_singletons(cfgs); time_driver(&mut singletons, cfgs); pin_features(&mut singletons); let mut g = TokenStream::new(); g.extend(generate_singletons(&singletons)); g.extend(generate_pincm_mapping()); g.extend(generate_pin()); g.extend(generate_timers()); g.extend(generate_interrupts()); g.extend(generate_peripheral_instances()); g.extend(generate_pin_trait_impls()); g.extend(generate_groups()); g.extend(generate_dma_channel_count()); g.extend(generate_adc_constants(cfgs)); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); fs::write(&out_file, g.to_string()).unwrap(); rustfmt(&out_file); } fn get_chip_cfgs(chip_name: &str) -> Vec { let mut cfgs = Vec::new(); // GPIO on C110x is special as it does not belong to an interrupt group. if chip_name.starts_with("mspm0c1103") || chip_name.starts_with("mspm0c1104") || chip_name.starts_with("msps003f") { cfgs.push("mspm0c110x".to_string()); } if chip_name.starts_with("mspm0c1105") || chip_name.starts_with("mspm0c1106") { cfgs.push("mspm0c1105_c1106".to_string()); } // Family ranges (temporary until int groups are generated) // // TODO: Remove this once int group stuff is generated. if chip_name.starts_with("mspm0g110") { cfgs.push("mspm0g110x".to_string()); } if chip_name.starts_with("mspm0g150") { cfgs.push("mspm0g150x".to_string()); } if chip_name.starts_with("mspm0g151") { cfgs.push("mspm0g151x".to_string()); } if chip_name.starts_with("mspm0g310") { cfgs.push("mspm0g310x".to_string()); } if chip_name.starts_with("mspm0g350") { cfgs.push("mspm0g350x".to_string()); } if chip_name.starts_with("mspm0g351") { cfgs.push("mspm0g351x".to_string()); } if chip_name.starts_with("mspm0g518") { cfgs.push("mspm0g518x".to_string()); } if chip_name.starts_with("mspm0h321") { cfgs.push("mspm0h321x".to_string()); } if chip_name.starts_with("mspm0l110") { cfgs.push("mspm0l110x".to_string()); } if chip_name.starts_with("mspm0l122") { cfgs.push("mspm0l122x".to_string()); } if chip_name.starts_with("mspm0l130") { cfgs.push("mspm0l130x".to_string()); } if chip_name.starts_with("mspm0l134") { cfgs.push("mspm0l134x".to_string()); } if chip_name.starts_with("mspm0l222") { cfgs.push("mspm0l222x".to_string()); } cfgs } /// Interrupt groups use a weakly linked symbols and #[linkage = "extern_weak"] is nightly we need to /// do some linker magic to create weak linkage. fn interrupt_group_linker_magic() { let mut file = String::new(); for group in METADATA.interrupt_groups { for interrupt in group.interrupts.iter() { let name = interrupt.name; writeln!(&mut file, "PROVIDE({name} = DefaultHandler);").unwrap(); } } let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("interrupt_group.x"); fs::write(&out_file, file).unwrap(); } fn generate_groups() -> TokenStream { let group_vectors = METADATA.interrupt_groups.iter().map(|group| { let vectors = group.interrupts.iter().map(|interrupt| { let fn_name = Ident::new(interrupt.name, Span::call_site()); quote! { pub(crate) fn #fn_name(); } }); quote! { #(#vectors)* } }); let groups = METADATA.interrupt_groups.iter().map(|group| { let interrupt_group_name = Ident::new(group.name, Span::call_site()); let group_enum = Ident::new(&format!("Group{}", &group.name[5..]), Span::call_site()); let group_number = Literal::u32_unsuffixed(group.number); let matches = group.interrupts.iter().map(|interrupt| { let variant = Ident::new(&interrupt.name, Span::call_site()); quote! { #group_enum::#variant => unsafe { group_vectors::#variant() }, } }); quote! { #[cfg(feature = "rt")] #[crate::pac::interrupt] fn #interrupt_group_name() { use crate::pac::#group_enum; let group = crate::pac::CPUSS.int_group(#group_number); let stat = group.iidx().read().stat(); // check for spurious interrupts if stat == crate::pac::cpuss::vals::Iidx::NO_INTR { return; } // MUST subtract by 1 because NO_INTR offsets IIDX values. let iidx = stat.to_bits() - 1; let Ok(group) = #group_enum::try_from(iidx as u8) else { return; }; match group { #(#matches)* } } } }); quote! { #(#groups)* #[cfg(feature = "rt")] mod group_vectors { unsafe extern "Rust" { #(#group_vectors)* } } } } fn generate_dma_channel_count() -> TokenStream { let count = METADATA.dma_channels.len(); quote! { pub const DMA_CHANNELS: usize = #count; } } fn generate_adc_constants(cfgs: &mut CfgSet) -> TokenStream { let vrsel = METADATA.adc_vrsel; let memctl = METADATA.adc_memctl; cfgs.declare("adc_neg_vref"); match vrsel { 3 => (), 5 => cfgs.enable("adc_neg_vref"), _ => panic!("Unsupported ADC VRSEL value: {vrsel}"), } quote! { pub const ADC_VRSEL: u8 = #vrsel; pub const ADC_MEMCTL: u8 = #memctl; } } #[derive(Debug, Clone)] struct Singleton { name: String, /// `#[cfg]` guard which enables this singleton instance to be obtained. cfg: Option, } impl PartialEq for Singleton { fn eq(&self, other: &Self) -> bool { self.name == other.name } } impl Eq for Singleton {} impl PartialOrd for Singleton { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Singleton { fn cmp(&self, other: &Self) -> Ordering { self.name.cmp(&other.name) } } fn get_singletons(cfgs: &mut common::CfgSet) -> Vec { let mut singletons = Vec::::new(); for peripheral in METADATA.peripherals { // Some peripherals do not generate a singleton, but generate a singleton for each pin. let skip_peripheral_singleton = match peripheral.kind { "gpio" => { // Also enable ports that are present. match peripheral.name { "GPIOB" => cfgs.enable("gpio_pb"), "GPIOC" => cfgs.enable("gpio_pc"), _ => (), } true } // Each channel gets a singleton, handled separately. "dma" => true, // These peripherals do not exist as singletons, and have no signals but are managed // by the HAL. "iomux" | "cpuss" => true, // Unicomm instances get their own singletons, but we need to enable a cfg for unicomm drivers. "unicomm" => { cfgs.enable("unicomm"); false } // TODO: Remove after TIMB is fixed "tim" if peripheral.name.starts_with("TIMB") => true, _ => false, }; if !skip_peripheral_singleton { singletons.push(Singleton { name: peripheral.name.to_string(), cfg: None, }); } // Generate each GPIO pin singleton if peripheral.name.starts_with("GPIO") { for pin in peripheral.pins { let singleton = make_valid_identifier(&pin.signal); singletons.push(singleton); } } } // DMA channels get their own singletons for dma_channel in METADATA.dma_channels.iter() { singletons.push(Singleton { name: format!("DMA_CH{}", dma_channel.number), cfg: None, }); } singletons.sort_by(|a, b| a.name.cmp(&b.name)); singletons } fn make_valid_identifier(s: &str) -> Singleton { let name = s.replace('+', "_P").replace("-", "_N"); Singleton { name, cfg: None } } fn generate_pincm_mapping() -> TokenStream { let pincms = METADATA.pins.iter().map(|mapping| { let port_letter = mapping.pin.strip_prefix("P").unwrap(); let port_base = (port_letter.chars().next().unwrap() as u8 - b'A') * 32; // This assumes all ports are single letter length. // This is fine unless TI releases a part with 833+ GPIO pins. let pin_number = mapping.pin[2..].parse::().unwrap(); let num = port_base + pin_number; // But subtract 1 since pincm indices start from 0, not 1. let pincm = Literal::u8_unsuffixed(mapping.pincm - 1); quote! { #num => #pincm } }); quote! { #[doc = "Get the mapping from GPIO pin port to IOMUX PINCM index. This is required since the mapping from IO to PINCM index is not consistent across parts."] pub(crate) fn gpio_pincm(pin_port: u8) -> u8 { match pin_port { #(#pincms),*, _ => unreachable!(), } } } } fn generate_pin() -> TokenStream { let pin_impls = METADATA.pins.iter().map(|pin| { let name = Ident::new(&pin.pin, Span::call_site()); let port_letter = pin.pin.strip_prefix("P").unwrap(); let port_letter = port_letter.chars().next().unwrap(); let pin_number = Literal::u8_unsuffixed(pin.pin[2..].parse::().unwrap()); let port = Ident::new(&format!("Port{}", port_letter), Span::call_site()); // TODO: Feature gate pins that can be used as NRST quote! { impl_pin!(#name, crate::gpio::Port::#port, #pin_number); } }); quote! { #(#pin_impls)* } } fn time_driver(singletons: &mut Vec, cfgs: &mut CfgSet) { // Timer features for (timer, _) in TIMERS.iter() { let name = timer.to_lowercase(); cfgs.declare(&format!("time_driver_{}", name)); } 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"), }; // Verify the selected timer is available let selected_timer = match time_driver.as_ref().map(|x| x.as_ref()) { None => "", // TODO: Fix TIMB0 // Some("timb0") => "TIMB0", Some("timg0") => "TIMG0", Some("timg1") => "TIMG1", Some("timg2") => "TIMG2", Some("timg3") => "TIMG3", Some("timg4") => "TIMG4", Some("timg5") => "TIMG5", Some("timg6") => "TIMG6", Some("timg7") => "TIMG7", Some("timg8") => "TIMG8", Some("timg9") => "TIMG9", Some("timg10") => "TIMG10", Some("timg11") => "TIMG11", Some("timg14") => "TIMG14", Some("tima0") => "TIMA0", Some("tima1") => "TIMA1", Some("any") => { // Order of timer candidates: // 1. Basic timers // 2. 16-bit, 2 channel // 3. 16-bit, 2 channel with shadow registers // 4. 16-bit, 4 channel // 5. 16-bit with QEI // 6. Advanced timers // // TODO: 32-bit timers are not considered yet [ // basic timers. No PWM pins // "TIMB0", // 16-bit, 2 channel "TIMG0", "TIMG1", "TIMG2", "TIMG3", // 16-bit, 2 channel with shadow registers "TIMG4", "TIMG5", "TIMG6", "TIMG7", // 16-bit, 4 channel "TIMG14", // 16-bit with QEI "TIMG8", "TIMG9", "TIMG10", "TIMG11", // Advanced timers "TIMA0", "TIMA1", ] .iter() .find(|tim| singletons.iter().any(|s| s.name == **tim)) .expect("Could not find any timer") } _ => panic!("unknown time_driver {:?}", time_driver), }; if !selected_timer.is_empty() { cfgs.enable(format!("time_driver_{}", selected_timer.to_lowercase())); } // Apply cfgs to each timer and it's pins for singleton in singletons.iter_mut() { if singleton.name.starts_with("TIM") { // Remove suffixes for pin singletons. let name = if singleton.name.contains("_CCP") { singleton.name.split_once("_CCP").unwrap().0 } else if singleton.name.contains("_FAULT") { singleton.name.split_once("_FAULT").unwrap().0 } else if singleton.name.contains("_IDX") { singleton.name.split_once("_IDX").unwrap().0 } else { &singleton.name }; let feature = format!("time-driver-{}", name.to_lowercase()); if singleton.name.contains(selected_timer) { singleton.cfg = Some(quote! { #[cfg(not(all(feature = "time-driver-any", feature = #feature)))] }); } else { singleton.cfg = Some(quote! { #[cfg(not(feature = #feature))] }); } } } } fn pin_features(singletons: &mut Vec) { let sysctl = METADATA .peripherals .iter() .find(|p| p.name == "SYSCTL") .expect("no SYSCTL peripheral"); // Some packages make NRST share a physical pin with a GPIO. if let Some(pin) = sysctl.pins.iter().find(|p| p.signal == "NRST" && p.pin != "NRST") { let pin = singletons .iter_mut() .find(|s| s.name == pin.pin) .expect("Could not find NRST pin to cfg gate"); pin.cfg = Some(quote! { #[cfg(feature = "nrst-pin-as-gpio")] }); } let debugss = METADATA .peripherals .iter() .find(|p| p.name == "DEBUGSS") .expect("Could not find DEBUGSS peripheral"); for pin in debugss.pins.iter() { let pin = singletons .iter_mut() .find(|s| s.name == pin.pin) .expect("Could not find SWD pin to cfg gate"); pin.cfg = Some(quote! { #[cfg(feature = "swd-pins-as-gpio")] }); } } fn generate_singletons(singletons: &[Singleton]) -> TokenStream { let singletons_peripherals_struct = singletons .iter() .map(|s| { let cfg = s.cfg.clone().unwrap_or_default(); let ident = format_ident!("{}", s.name); quote! { #cfg #ident } }) .collect::>(); let singletons_peripherals_def = singletons .iter() .map(|s| { let ident = format_ident!("{}", s.name); quote! { #ident } }) .collect::>(); quote! { embassy_hal_internal::peripherals_definition!(#(#singletons_peripherals_def),*); embassy_hal_internal::peripherals_struct!(#(#singletons_peripherals_struct),*); } } fn generate_timers() -> TokenStream { // Generate timers let timer_impls = METADATA .peripherals .iter() .filter(|p| p.name.starts_with("TIM")) // TODO: Fix TIMB when used at time driver. .filter(|p| !p.name.starts_with("TIMB")) .map(|peripheral| { let name = Ident::new(&peripheral.name, Span::call_site()); let timers = &*TIMERS; let timer = timers.get(peripheral.name).expect("Timer does not exist"); assert!(timer.bits == 16 || timer.bits == 32); let bits = if timer.bits == 16 { quote! { Bits16 } } else { quote! { Bits32 } }; quote! { impl_timer!(#name, #bits); } }); quote! { #(#timer_impls)* } } fn generate_interrupts() -> TokenStream { // Generate interrupt module let interrupts: Vec = METADATA .interrupts .iter() .map(|interrupt| Ident::new(interrupt.name, Span::call_site())) .collect(); let group_interrupt_enables = METADATA .interrupts .iter() .filter(|interrupt| interrupt.name.contains("GROUP")) .map(|interrupt| { let name = Ident::new(interrupt.name, Span::call_site()); quote! { crate::interrupt::typelevel::#name::enable(); } }); // Generate interrupt enables for groups quote! { embassy_hal_internal::interrupt_mod! { #(#interrupts),* } pub fn enable_group_interrupts(_cs: critical_section::CriticalSection) { use crate::interrupt::typelevel::Interrupt; // This is empty for C1105/6 #[allow(unused_unsafe)] unsafe { #(#group_interrupt_enables)* } } } } fn generate_peripheral_instances() -> TokenStream { let mut impls = Vec::::new(); for peripheral in METADATA.peripherals { let peri = format_ident!("{}", peripheral.name); let fifo_size = peripheral.sys_fentries; let tokens = match peripheral.kind { "uart" => Some(quote! { impl_uart_instance!(#peri); }), "i2c" => Some(quote! { impl_i2c_instance!(#peri, #fifo_size); }), "wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }), "adc" => Some(quote! { impl_adc_instance!(#peri); }), "mathacl" => Some(quote! { impl_mathacl_instance!(#peri); }), _ => None, }; if let Some(tokens) = tokens { impls.push(tokens); } } // DMA channels for dma_channel in METADATA.dma_channels.iter() { let peri = format_ident!("DMA_CH{}", dma_channel.number); let num = dma_channel.number; if dma_channel.full { impls.push(quote! { impl_full_dma_channel!(#peri, #num); }); } else { impls.push(quote! { impl_dma_channel!(#peri, #num); }); } } quote! { #(#impls)* } } fn generate_pin_trait_impls() -> TokenStream { let mut impls = Vec::::new(); for peripheral in METADATA.peripherals { for pin in peripheral.pins { let key = (peripheral.kind, pin.signal); let pin_name = format_ident!("{}", pin.pin); let peri = format_ident!("{}", peripheral.name); let pf = pin.pf; // Will be filled in when uart implementation is finished let _ = pin_name; let _ = peri; let _ = pf; let tokens = match key { ("uart", "TX") => Some(quote! { impl_uart_tx_pin!(#peri, #pin_name, #pf); }), ("uart", "RX") => Some(quote! { impl_uart_rx_pin!(#peri, #pin_name, #pf); }), ("uart", "CTS") => Some(quote! { impl_uart_cts_pin!(#peri, #pin_name, #pf); }), ("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }), ("i2c", "SDA") => Some(quote! { impl_i2c_sda_pin!(#peri, #pin_name, #pf); }), ("i2c", "SCL") => Some(quote! { impl_i2c_scl_pin!(#peri, #pin_name, #pf); }), ("adc", s) => { let signal = s.parse::().unwrap(); Some(quote! { impl_adc_pin!(#peri, #pin_name, #signal); }) } _ => None, }; if let Some(tokens) = tokens { impls.push(tokens); } } } quote! { #(#impls)* } } fn select_gpio_features(cfgs: &mut CfgSet) { cfgs.declare_all(&[ "gpioa_interrupt", "gpioa_group", "gpiob_interrupt", "gpiob_group", "gpioc_group", ]); for interrupt in METADATA.interrupts.iter() { match interrupt.name { "GPIOA" => cfgs.enable("gpioa_interrupt"), "GPIOB" => cfgs.enable("gpiob_interrupt"), _ => (), } } for group in METADATA.interrupt_groups.iter() { for interrupt in group.interrupts { match interrupt.name { "GPIOA" => cfgs.enable("gpioa_group"), "GPIOB" => cfgs.enable("gpiob_group"), "GPIOC" => cfgs.enable("gpioc_group"), _ => (), } } } } /// 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(); } } } } #[allow(dead_code)] struct TimerDesc { bits: u8, /// Is there an 8-bit prescaler prescaler: bool, /// Is there a repeat counter repeat_counter: bool, ccp_channels_internal: u8, ccp_channels_external: u8, external_pwm_channels: u8, phase_load: bool, shadow_load: bool, shadow_ccs: bool, deadband: bool, fault_handler: bool, qei_hall: bool, } /// Description of all timer instances. const TIMERS: LazyLock> = LazyLock::new(|| { let mut map = HashMap::new(); map.insert( "TIMB0".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 0, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG0".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG1".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG2".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG3".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG4".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG5".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG6".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG7".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG8".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG9".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG10".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG11".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG12".into(), TimerDesc { bits: 32, prescaler: false, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG13".into(), TimerDesc { bits: 32, prescaler: false, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG14".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 4, ccp_channels_external: 4, external_pwm_channels: 4, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMA0".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: true, ccp_channels_internal: 4, ccp_channels_external: 2, external_pwm_channels: 8, phase_load: true, shadow_load: true, shadow_ccs: true, deadband: true, fault_handler: true, qei_hall: false, }, ); map.insert( "TIMA1".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: true, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 4, phase_load: true, shadow_load: true, shadow_ccs: true, deadband: true, fault_handler: true, qei_hall: false, }, ); map }); 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), }, } } } ================================================ FILE: embassy-mspm0/build_common.rs ================================================ // 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")); } ================================================ FILE: embassy-mspm0/src/adc.rs ================================================ #![macro_use] use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::{PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; use crate::interrupt::{Interrupt, InterruptExt}; use crate::mode::{Async, Blocking, Mode}; use crate::pac::adc::{Adc as Regs, vals}; use crate::{Peri, interrupt}; /// Interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { // Mis is cleared upon reading iidx let iidx = T::info().regs.cpu_int(0).iidx().read().stat(); // TODO: Running in sequence mode, we get an interrupt per finished result. It would be // nice to wake up only after all results are finished. if vals::CpuIntIidxStat::MEMRESIFG0 <= iidx && iidx <= vals::CpuIntIidxStat::MEMRESIFG23 { T::state().waker.wake(); } } } // Constants from the metapac crate const ADC_VRSEL: u8 = crate::_generated::ADC_VRSEL; const ADC_MEMCTL: u8 = crate::_generated::ADC_MEMCTL; #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Conversion resolution of the ADC results. pub enum Resolution { /// 12-bits resolution BIT12, /// 10-bits resolution BIT10, /// 8-bits resolution BIT8, } impl Resolution { /// Number of bits of the resolution. pub fn bits(&self) -> u8 { match self { Resolution::BIT12 => 12, Resolution::BIT10 => 10, Resolution::BIT8 => 8, } } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Reference voltage (Vref) selection for the ADC channels. pub enum Vrsel { /// VDDA reference VddaVssa = 0, /// External reference from pin ExtrefVrefm = 1, /// Internal reference IntrefVssa = 2, /// VDDA and VREFM connected to VREF+ and VREF- of ADC #[cfg(adc_neg_vref)] VddaVrefm = 3, /// INTREF and VREFM connected to VREF+ and VREF- of ADC #[cfg(adc_neg_vref)] IntrefVrefm = 4, } /// ADC configuration. #[derive(Copy, Clone)] #[non_exhaustive] pub struct Config { /// Resolution of the ADC conversion. The number of bits used to represent an ADC measurement. pub resolution: Resolution, /// ADC voltage reference selection. /// /// This value is used when reading a single channel. When reading a sequence /// the vr_select is provided per channel. pub vr_select: Vrsel, /// The sample time in number of ADC sample clock cycles. pub sample_time: u16, } impl Default for Config { fn default() -> Self { Self { resolution: Resolution::BIT12, vr_select: Vrsel::VddaVssa, sample_time: 50, } } } /// ADC (Analog to Digial Converter) Driver. pub struct Adc<'d, T: Instance, M: Mode> { #[allow(unused)] adc: crate::Peri<'d, T>, info: &'static Info, state: &'static State, config: Config, _phantom: PhantomData, } impl<'d, T: Instance> Adc<'d, T, Blocking> { /// A new blocking ADC driver instance. pub fn new_blocking(peri: Peri<'d, T>, config: Config) -> Self { let mut this = Self { adc: peri, info: T::info(), state: T::state(), config, _phantom: PhantomData, }; this.setup(); this } } impl<'d, T: Instance> Adc<'d, T, Async> { /// A new asynchronous ADC driver instance. pub fn new_async( peri: Peri<'d, T>, config: Config, _irqs: impl interrupt::typelevel::Binding> + 'd, ) -> Self { let mut this = Self { adc: peri, info: T::info(), state: T::state(), config, _phantom: PhantomData, }; this.setup(); unsafe { this.info.interrupt.enable(); } this } } impl<'d, T: Instance, M: Mode> Adc<'d, T, M> { const SINGLE_CHANNEL: u8 = 0; fn setup(&mut self) { let config = &self.config; assert!( (config.vr_select as u8) < ADC_VRSEL, "Reference voltage selection out of bounds" ); // Reset adc self.info.regs.gprcm(0).rstctl().write(|reg| { reg.set_resetstkyclr(true); reg.set_resetassert(true); reg.set_key(vals::ResetKey::KEY); }); // Power up adc self.info.regs.gprcm(0).pwren().modify(|reg| { reg.set_enable(true); reg.set_key(vals::PwrenKey::KEY); }); // Wait for cycles similar to TI power setup cortex_m::asm::delay(16); // Set clock config self.info.regs.gprcm(0).clkcfg().modify(|reg| { reg.set_key(vals::ClkcfgKey::KEY); reg.set_sampclk(vals::Sampclk::SYSOSC); }); self.info.regs.ctl0().modify(|reg| { reg.set_sclkdiv(vals::Sclkdiv::DIV_BY_4); }); self.info.regs.clkfreq().modify(|reg| { reg.set_frange(vals::Frange::RANGE24TO32); }); // Init single conversion with software trigger and auto sampling // // We use sequence to support sequence operation in the future, but only set up a single // channel self.info.regs.ctl1().modify(|reg| { reg.set_conseq(vals::Conseq::SEQUENCE); reg.set_sampmode(vals::Sampmode::AUTO); reg.set_trigsrc(vals::Trigsrc::SOFTWARE); }); let res = match config.resolution { Resolution::BIT12 => vals::Res::BIT_12, Resolution::BIT10 => vals::Res::BIT_10, Resolution::BIT8 => vals::Res::BIT_8, }; self.info.regs.ctl2().modify(|reg| { // Startadd detemines the channel used in single mode. reg.set_startadd(Self::SINGLE_CHANNEL); reg.set_endadd(Self::SINGLE_CHANNEL); reg.set_res(res); reg.set_df(false); }); // Set the sample time used by all channels for now self.info.regs.scomp0().modify(|reg| { reg.set_val(config.sample_time); }); } fn setup_blocking_channel(&mut self, channel: &Peri<'d, impl AdcChannel>) { channel.setup(); // CTL0.ENC must be 0 to write the MEMCTL register while self.info.regs.ctl0().read().enc() { // Wait until the ADC is not in conversion mode } // Conversion mem config let vrsel = vals::Vrsel::from_bits(self.config.vr_select as u8); self.info.regs.memctl(Self::SINGLE_CHANNEL as usize).modify(|reg| { reg.set_chansel(channel.channel()); reg.set_vrsel(vrsel); reg.set_stime(vals::Stime::SEL_SCOMP0); reg.set_avgen(false); reg.set_bcsen(false); reg.set_trig(vals::Trig::AUTO_NEXT); reg.set_wincomp(false); }); self.info.regs.ctl2().modify(|reg| { // Set end address to the number of used channels reg.set_endadd(Self::SINGLE_CHANNEL); }); } fn enable_conversion(&mut self) { // Enable conversion self.info.regs.ctl0().modify(|reg| { reg.set_enc(true); }); } fn start_conversion(&mut self) { // Start conversion self.info.regs.ctl1().modify(|reg| { reg.set_sc(vals::Sc::START); }); } fn conversion_result(&mut self, channel_id: usize) -> u16 { // Read result self.info.regs.memres(channel_id).read().data() } /// Read one ADC channel in blocking mode using the config provided at initialization. pub fn blocking_read(&mut self, channel: &Peri<'d, impl AdcChannel>) -> u16 { self.setup_blocking_channel(channel); self.enable_conversion(); self.start_conversion(); while self.info.regs.ctl0().read().enc() {} self.conversion_result(Self::SINGLE_CHANNEL as usize) } } impl<'d, T: Instance> Adc<'d, T, Async> { /// Maximum length allowed for [`Self::read_sequence`]. pub const MAX_SEQUENCE_LEN: usize = ADC_MEMCTL as usize; async fn wait_for_conversion(&self) { let info = self.info; let state = self.state; poll_fn(move |cx| { state.waker.register(cx.waker()); if !info.regs.ctl0().read().enc() { Poll::Ready(()) } else { Poll::Pending } }) .await; } fn setup_async_channel(&self, id: usize, channel: &Peri<'d, impl AdcChannel>, vrsel: Vrsel) { let vrsel = vals::Vrsel::from_bits(vrsel as u8); // Conversion mem config self.info.regs.memctl(id).modify(|reg| { reg.set_chansel(channel.channel()); reg.set_vrsel(vrsel); reg.set_stime(vals::Stime::SEL_SCOMP0); reg.set_avgen(false); reg.set_bcsen(false); reg.set_trig(vals::Trig::AUTO_NEXT); reg.set_wincomp(false); }); // Clear interrupt status self.info.regs.cpu_int(0).iclr().write(|reg| { reg.set_memresifg(id, true); }); // Enable interrupt self.info.regs.cpu_int(0).imask().modify(|reg| { reg.set_memresifg(id, true); }); } /// Read one ADC channel asynchronously using the config provided at initialization. pub async fn read_channel(&mut self, channel: &Peri<'d, impl AdcChannel>) -> u16 { channel.setup(); // CTL0.ENC must be 0 to write the MEMCTL register self.wait_for_conversion().await; self.info.regs.ctl2().modify(|reg| { // Set end address to the number of used channels reg.set_endadd(Self::SINGLE_CHANNEL); }); self.setup_async_channel(Self::SINGLE_CHANNEL as usize, channel, self.config.vr_select); self.enable_conversion(); self.start_conversion(); self.wait_for_conversion().await; self.conversion_result(Self::SINGLE_CHANNEL as usize) } /// Read one or multiple ADC channels using the Vrsel provided per channel. /// /// `sequence` iterator and `readings` must have the same length. /// /// Example /// ```rust,ignore /// use embassy_mspm0::adc::{Adc, AdcChannel, Vrsel}; /// /// let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); /// let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PA20.into(), Vrsel::VddaVssa)]; /// let mut readings = [0u16; 2]; /// /// adc.read_sequence(sequence.into_iter(), &mut readings).await; /// defmt::info!("Measurements: {}", readings); /// ``` pub async fn read_sequence<'a>( &mut self, sequence: impl ExactSizeIterator>, Vrsel)>, readings: &mut [u16], ) where 'd: 'a, { 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() <= Self::MAX_SEQUENCE_LEN, "Asynchronous read sequence cannot be more than {} in length", Self::MAX_SEQUENCE_LEN ); // CTL0.ENC must be 0 to write the MEMCTL register self.wait_for_conversion().await; self.info.regs.ctl2().modify(|reg| { // Set end address to the number of used channels reg.set_endadd((sequence.len() - 1) as u8); }); for (i, (channel, vrsel)) in sequence.enumerate() { self.setup_async_channel(i, channel, vrsel); } self.enable_conversion(); self.start_conversion(); self.wait_for_conversion().await; for (i, r) in readings.iter_mut().enumerate() { *r = self.conversion_result(i); } } } /// Peripheral instance trait. #[allow(private_bounds)] pub trait Instance: PeripheralType + SealedInstance { type Interrupt: crate::interrupt::typelevel::Interrupt; } /// Peripheral state. pub(crate) struct State { waker: AtomicWaker, } impl State { pub const fn new() -> Self { Self { waker: AtomicWaker::new(), } } } /// Peripheral information. pub(crate) struct Info { pub(crate) regs: Regs, pub(crate) interrupt: Interrupt, } /// Peripheral instance trait. pub(crate) trait SealedInstance { fn info() -> &'static Info; fn state() -> &'static State; } macro_rules! impl_adc_instance { ($instance: ident) => { impl crate::adc::SealedInstance for crate::peripherals::$instance { fn info() -> &'static crate::adc::Info { use crate::adc::Info; use crate::interrupt::typelevel::Interrupt; static INFO: Info = Info { regs: crate::pac::$instance, interrupt: crate::interrupt::typelevel::$instance::IRQ, }; &INFO } fn state() -> &'static crate::adc::State { use crate::adc::State; static STATE: State = State::new(); &STATE } } impl crate::adc::Instance for crate::peripherals::$instance { type Interrupt = crate::interrupt::typelevel::$instance; } }; } /// 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 { pub(crate) channel: u8, pub(crate) _phantom: PhantomData, } impl_peripheral!(AnyAdcChannel); impl AdcChannel for AnyAdcChannel {} impl SealedAdcChannel for AnyAdcChannel { fn channel(&self) -> u8 { self.channel } } impl AnyAdcChannel { #[allow(unused)] pub(crate) fn get_hw_channel(&self) -> u8 { self.channel } } /// ADC channel. #[allow(private_bounds)] pub trait AdcChannel: PeripheralType + Into> + SealedAdcChannel + Sized {} pub(crate) trait SealedAdcChannel { fn setup(&self) {} fn channel(&self) -> u8; } 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 { fn setup(&self) { crate::gpio::SealedPin::set_as_analog(self); } fn channel(&self) -> u8 { $ch } } impl From for crate::adc::AnyAdcChannel { fn from(val: crate::peripherals::$pin) -> Self { crate::adc::SealedAdcChannel::::setup(&val); Self { channel: crate::adc::SealedAdcChannel::::channel(&val), _phantom: core::marker::PhantomData, } } } }; } ================================================ FILE: embassy-mspm0/src/dma.rs ================================================ //! Direct Memory Access (DMA) #![macro_use] use core::future::Future; use core::mem; use core::pin::Pin; use core::sync::atomic::{Ordering, compiler_fence}; use core::task::{Context, Poll}; use critical_section::CriticalSection; use embassy_hal_internal::interrupt::InterruptExt; use embassy_hal_internal::{PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; use mspm0_metapac::common::{RW, Reg}; use mspm0_metapac::dma::regs; use mspm0_metapac::dma::vals::{self, Autoen, Em, Incr, Preirq, Wdth}; use crate::{Peri, interrupt, pac}; /// The burst size of a DMA transfer. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BurstSize { /// The whole block transfer is completed in one transfer without interruption. Complete, /// The burst size is 8, after 9 transfers the block transfer is interrupted and the priority /// is reevaluated. _8, /// The burst size is 16, after 17 transfers the block transfer is interrupted and the priority /// is reevaluated. _16, /// The burst size is 32, after 32 transfers the block transfer is interrupted and the priority /// is reevaluated. _32, } /// DMA channel. #[allow(private_bounds)] pub trait Channel: Into + PeripheralType {} /// Full DMA channel. #[allow(private_bounds)] pub trait FullChannel: Channel + Into {} /// Type-erased DMA channel. pub struct AnyChannel { pub(crate) id: u8, } impl_peripheral!(AnyChannel); impl SealedChannel for AnyChannel { fn id(&self) -> u8 { self.id } } impl Channel for AnyChannel {} /// Type-erased full DMA channel. pub struct AnyFullChannel { pub(crate) id: u8, } impl_peripheral!(AnyFullChannel); impl SealedChannel for AnyFullChannel { fn id(&self) -> u8 { self.id } } impl Channel for AnyFullChannel {} impl FullChannel for AnyFullChannel {} impl From for AnyChannel { fn from(value: AnyFullChannel) -> Self { Self { id: value.id } } } #[allow(private_bounds)] pub trait Word: SealedWord { /// Size in bytes for the width. fn size() -> isize; } impl SealedWord for u8 { fn width() -> vals::Wdth { vals::Wdth::BYTE } } impl Word for u8 { fn size() -> isize { 1 } } impl SealedWord for u16 { fn width() -> vals::Wdth { vals::Wdth::HALF } } impl Word for u16 { fn size() -> isize { 2 } } impl SealedWord for u32 { fn width() -> vals::Wdth { vals::Wdth::WORD } } impl Word for u32 { fn size() -> isize { 4 } } impl SealedWord for u64 { fn width() -> vals::Wdth { vals::Wdth::LONG } } impl Word for u64 { fn size() -> isize { 8 } } // TODO: u128 (LONGLONG) support. G350x does support it, but other parts do not such as C110x. More metadata is // needed to properly enable this. // impl SealedWord for u128 { // fn width() -> vals::Wdth { // vals::Wdth::LONGLONG // } // } // impl Word for u128 { // fn size() -> isize { // 16 // } // } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// The DMA transfer is too large. /// /// The hardware limits the DMA to 16384 transfers per channel at a time. This means that transferring /// 16384 `u8` and 16384 `u64` are equivalent, since the DMA must copy 16384 values. TooManyTransfers, } /// DMA transfer mode for basic channels. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum TransferMode { /// Each DMA trigger will transfer a single value. Single, /// Each DMA trigger will transfer the complete block with one trigger. Block, } /// DMA transfer options. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct TransferOptions { /// DMA transfer mode. pub mode: TransferMode, // TODO: Read and write stride. } impl Default for TransferOptions { fn default() -> Self { Self { mode: TransferMode::Single, } } } /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { channel: Peri<'a, AnyChannel>, } impl<'a> Transfer<'a> { /// Software trigger source. /// /// Using this trigger source means that a transfer will start immediately rather than waiting for /// a hardware event. This can be useful if you want to do a DMA accelerated memcpy. pub const SOFTWARE_TRIGGER: u8 = 0; /// Create a new read DMA transfer. pub unsafe fn new_read( channel: Peri<'a, impl Channel>, trigger_source: u8, src: *mut SW, dst: &'a mut [DW], options: TransferOptions, ) -> Result { Self::new_read_raw(channel, trigger_source, src, dst, options) } /// Create a new read DMA transfer, using raw pointers. pub unsafe fn new_read_raw( channel: Peri<'a, impl Channel>, trigger_source: u8, src: *mut SW, dst: *mut [DW], options: TransferOptions, ) -> Result { verify_transfer::(dst)?; let channel = channel.into(); channel.configure( trigger_source, src.cast(), SW::width(), dst.cast(), DW::width(), dst.len() as u16, false, true, options, ); channel.start(); Ok(Self { channel }) } /// Create a new write DMA transfer. pub unsafe fn new_write( channel: Peri<'a, impl Channel>, trigger_source: u8, src: &'a [SW], dst: *mut DW, options: TransferOptions, ) -> Result { Self::new_write_raw(channel, trigger_source, src, dst, options) } /// Create a new write DMA transfer, using raw pointers. pub unsafe fn new_write_raw( channel: Peri<'a, impl Channel>, trigger_source: u8, src: *const [SW], dst: *mut DW, options: TransferOptions, ) -> Result { verify_transfer::(src)?; let channel = channel.into(); channel.configure( trigger_source, src.cast(), SW::width(), dst.cast(), DW::width(), src.len() as u16, true, false, options, ); channel.start(); Ok(Self { channel }) } // TODO: Copy between slices. /// Request the transfer to resume. pub fn resume(&mut self) { self.channel.resume(); } /// 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`]. pub fn is_running(&mut self) -> bool { self.channel.is_running() } /// Blocking wait until the transfer finishes. pub fn blocking_wait(mut self) { // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); while self.is_running() {} // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); // Prevent drop from being called since we ran to completion (drop will try to pause). mem::forget(self); } } impl<'a> Unpin for Transfer<'a> {} impl<'a> Future for Transfer<'a> { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let state: &ChannelState = &STATE[self.channel.id as usize]; state.waker.register(cx.waker()); // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); if self.channel.is_running() { Poll::Pending } else { Poll::Ready(()) } } } impl<'a> Drop for Transfer<'a> { fn drop(&mut self) { self.channel.request_pause(); while self.is_running() {} // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); } } // impl details fn verify_transfer(ptr: *const [W]) -> Result<(), Error> { if ptr.len() > (u16::MAX as usize) { return Err(Error::TooManyTransfers); } // TODO: Stride checks Ok(()) } fn convert_burst_size(value: BurstSize) -> vals::Burstsz { match value { BurstSize::Complete => vals::Burstsz::INFINITI, BurstSize::_8 => vals::Burstsz::BURST_8, BurstSize::_16 => vals::Burstsz::BURST_16, BurstSize::_32 => vals::Burstsz::BURST_32, } } fn convert_mode(mode: TransferMode) -> vals::Tm { match mode { TransferMode::Single => vals::Tm::SINGLE, TransferMode::Block => vals::Tm::BLOCK, } } const CHANNEL_COUNT: usize = crate::_generated::DMA_CHANNELS; static STATE: [ChannelState; CHANNEL_COUNT] = [const { ChannelState::new() }; CHANNEL_COUNT]; struct ChannelState { waker: AtomicWaker, } impl ChannelState { const fn new() -> Self { Self { waker: AtomicWaker::new(), } } } /// SAFETY: Must only be called once. /// /// Changing the burst size mid transfer may have some odd behavior. pub(crate) unsafe fn init(_cs: CriticalSection, burst_size: BurstSize, round_robin: bool) { pac::DMA.prio().modify(|prio| { prio.set_burstsz(convert_burst_size(burst_size)); prio.set_roundrobin(round_robin); }); pac::DMA.int_event(0).imask().modify(|w| { w.set_dataerr(true); w.set_addrerr(true); }); interrupt::DMA.enable(); } pub(crate) trait SealedWord { fn width() -> vals::Wdth; } pub(crate) trait SealedChannel { fn id(&self) -> u8; #[inline] fn tctl(&self) -> Reg { pac::DMA.trig(self.id() as usize).tctl() } #[inline] fn ctl(&self) -> Reg { pac::DMA.chan(self.id() as usize).ctl() } #[inline] fn sa(&self) -> Reg { pac::DMA.chan(self.id() as usize).sa() } #[inline] fn da(&self) -> Reg { pac::DMA.chan(self.id() as usize).da() } #[inline] fn sz(&self) -> Reg { pac::DMA.chan(self.id() as usize).sz() } #[inline] fn mask_interrupt(&self, enable: bool) { // Enabling interrupts is an RMW operation. critical_section::with(|_cs| { pac::DMA.int_event(0).imask().modify(|w| { w.set_ch(self.id() as usize, enable); }); }) } /// # Safety /// /// - `src` must be valid for the lifetime of the transfer. /// - `dst` must be valid for the lifetime of the transfer. unsafe fn configure( &self, trigger_sel: u8, src: *const u32, src_wdth: Wdth, dst: *const u32, dst_wdth: Wdth, transfer_count: u16, increment_src: bool, increment_dst: bool, options: TransferOptions, ) { // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); self.ctl().modify(|w| { // SLAU 5.2.5: // "The DMATSEL bits should be modified only when the DMACTLx.DMAEN bit is // 0; otherwise, unpredictable DMA triggers can occur." // // We also want to stop any transfers before setup. w.set_en(false); w.set_req(false); // Not every part supports auto enable, so force its value to 0. w.set_autoen(Autoen::NONE); w.set_preirq(Preirq::PREIRQ_DISABLE); w.set_srcwdth(src_wdth); w.set_dstwdth(dst_wdth); w.set_srcincr(if increment_src { Incr::INCREMENT } else { Incr::UNCHANGED }); w.set_dstincr(if increment_dst { Incr::INCREMENT } else { Incr::UNCHANGED }); w.set_em(Em::NORMAL); // Single and block will clear the enable bit when the transfers finish. w.set_tm(convert_mode(options.mode)); }); self.tctl().write(|w| { w.set_tsel(trigger_sel); // Basic channels do not implement cross triggering. w.set_tint(vals::Tint::EXTERNAL); }); self.sz().write(|w| { w.set_size(transfer_count); }); self.sa().write_value(src as u32); self.da().write_value(dst as u32); // Enable the channel. self.ctl().modify(|w| { // FIXME: Why did putting set_req later fix some transfers w.set_en(true); w.set_req(true); }); } fn start(&self) { self.mask_interrupt(true); // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); // Request the DMA transfer to start. self.ctl().modify(|w| { w.set_req(true); }); } fn resume(&self) { self.mask_interrupt(true); // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); self.ctl().modify(|w| { // w.set_en(true); w.set_req(true); }); } fn request_pause(&self) { // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); // Stop the transfer. // // SLAU846 5.2.6: // "A DMA block transfer in progress can be stopped by clearing the DMAEN bit" self.ctl().modify(|w| { // w.set_en(false); w.set_req(false); }); } fn is_running(&self) -> bool { // "Subsequent reads and writes cannot be moved ahead of preceding reads." compiler_fence(Ordering::SeqCst); let ctl = self.ctl().read(); // Is the transfer requested? ctl.req() // Is the channel enabled? && ctl.en() } } macro_rules! impl_dma_channel { ($instance: ident, $num: expr) => { impl crate::dma::SealedChannel for crate::peripherals::$instance { fn id(&self) -> u8 { $num } } impl From for crate::dma::AnyChannel { fn from(value: crate::peripherals::$instance) -> Self { use crate::dma::SealedChannel; Self { id: value.id() } } } impl crate::dma::Channel for crate::peripherals::$instance {} }; } // C1104 has no full DMA channels. #[allow(unused_macros)] macro_rules! impl_full_dma_channel { ($instance: ident, $num: expr) => { impl_dma_channel!($instance, $num); impl From for crate::dma::AnyFullChannel { fn from(value: crate::peripherals::$instance) -> Self { use crate::dma::SealedChannel; Self { id: value.id() } } } impl crate::dma::FullChannel for crate::peripherals::$instance {} }; } #[cfg(feature = "rt")] #[interrupt] fn DMA() { use crate::BitIter; let events = pac::DMA.int_event(0); let mis = events.mis().read(); // TODO: Handle DATAERR and ADDRERR? However we do not know which channel causes an error. if mis.dataerr() { panic!("DMA data error"); } else if mis.addrerr() { panic!("DMA address error") } // Ignore preirq interrupts (values greater than 16). for i in BitIter(mis.0 & 0x0000_FFFF) { if let Some(state) = STATE.get(i as usize) { state.waker.wake(); // Notify the future that the counter size hit zero events.imask().modify(|w| { w.set_ch(i as usize, false); }); } } } ================================================ FILE: embassy-mspm0/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-mspm0/src/gpio.rs ================================================ #![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::{Peri, PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; use crate::pac::gpio::vals::*; use crate::pac::gpio::{self}; #[cfg(all(feature = "rt", any(gpioa_interrupt, gpiob_interrupt)))] use crate::pac::interrupt; use crate::pac::{self}; /// Represents a digital input or output level. #[derive(Debug, Eq, PartialEq, Clone, Copy)] #[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, } } } /// 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, } /// A GPIO bank with up to 32 pins. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Port { /// Port A. PortA = 0, /// Port B. #[cfg(gpio_pb)] PortB = 1, /// Port C. #[cfg(gpio_pc)] PortC = 2, } /// 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> { pin: Peri<'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: Peri<'d, impl Pin>) -> Self { // Pin will be in disconnected state. Self { pin: pin.into() } } /// Set the pin's pull. #[inline] pub fn set_pull(&mut self, pull: Pull) { let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_pipd(matches!(pull, Pull::Down)); w.set_pipu(matches!(pull, Pull::Up)); }); } /// Put the pin into input mode. /// /// The pull setting is left unchanged. #[inline] pub fn set_as_input(&mut self) { let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_pf(GPIO_PF); w.set_hiz1(false); w.set_pc(true); w.set_inena(true); }); self.pin.block().doeclr31_0().write(|w| { w.set_dio(self.pin.bit_index(), true); }); } /// 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) { let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_pf(GPIO_PF); w.set_hiz1(false); w.set_pc(true); w.set_inena(false); }); self.pin.block().doeset31_0().write(|w| { w.set_dio(self.pin.bit_index(), true); }); } /// 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] pub fn set_as_input_output(&mut self) { let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_pf(GPIO_PF); w.set_hiz1(true); w.set_pc(true); w.set_inena(true); }); // Enable output driver (DOE) - required for open-drain to drive low self.pin.block().doeset31_0().write(|w| { w.set_dio(self.pin.bit_index(), true); }); self.set_pull(Pull::None); } /// 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] pub fn set_as_disconnected(&mut self) { let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_pf(DISCONNECT_PF); w.set_hiz1(false); w.set_pc(false); w.set_inena(false); }); self.set_pull(Pull::None); self.set_inversion(false); } /// Configure the logic inversion of this pin. /// /// Logic inversion applies to both the input and output path of this pin. #[inline] pub fn set_inversion(&mut self, invert: bool) { let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_inv(invert); }); } // TODO: drive strength, hysteresis, wakeup enable, wakeup compare /// Put the pin into the PF mode, unchecked. /// /// This puts the pin into the PF mode, with the request number. This is completely unchecked, /// it can attach the pin to literally any peripheral, so use with care. In addition the pin /// peripheral is connected in the iomux. /// /// The peripheral attached to the pin depends on the part in use. Consult the datasheet /// or technical reference manual for additional details. #[inline] pub fn set_pf_unchecked(&mut self, pf: u8) { // Per SLAU893 and SLAU846B, PF is only 6 bits assert_eq!(pf & 0xC0, 0, "PF is out of range"); let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); pincm.modify(|w| { w.set_pf(pf); // If the PF is manually set, connect the pin w.set_pc(true); }); } /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { self.pin.block().din31_0().read().dio(self.pin.bit_index()) } /// Get whether the pin input level is low. #[inline] pub fn is_low(&self) -> bool { !self.is_high() } /// 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.block().doutset31_0().write(|w| { w.set_dio(self.pin.bit_index() as usize, true); }); } /// Set the output as low. #[inline] pub fn set_low(&mut self) { self.pin.block().doutclr31_0().write(|w| { w.set_dio(self.pin.bit_index(), true); }); } /// Toggle pin output #[inline] pub fn toggle(&mut self) { self.pin.block().douttgl31_0().write(|w| { w.set_dio(self.pin.bit_index(), true); }) } /// 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(), } } /// Get the current pin output level. #[inline] pub fn get_output_level(&self) -> Level { self.is_set_high().into() } /// Is the output level high? #[inline] pub fn is_set_high(&self) -> bool { self.pin.block().dout31_0().read().dio(self.pin.bit_index()) } /// Is the output level low? #[inline] pub fn is_set_low(&self) -> bool { !self.is_set_high() } /// Wait until the pin is high. If it is already high, return immediately. #[inline] pub async fn wait_for_high(&mut self) { if self.is_high() { return; } self.wait_for_rising_edge().await } /// Wait until the pin is low. If it is already low, return immediately. #[inline] pub async fn wait_for_low(&mut self) { if self.is_low() { return; } self.wait_for_falling_edge().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(), Polarity::RISE).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(), Polarity::FALL).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(), Polarity::RISE_FALL).await } } impl<'d> Drop for Flex<'d> { #[inline] fn drop(&mut self) { self.set_as_disconnected(); } } /// 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: Peri<'d, impl Pin>, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(); pin.set_pull(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() } /// Configure the logic inversion of this pin. /// /// Logic inversion applies to the input path of this pin. #[inline] pub fn set_inversion(&mut self, invert: bool) { self.pin.set_inversion(invert) } /// 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 } } /// 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> { pin: Flex<'d>, } impl<'d> Output<'d> { /// Create GPIO output driver for a [Pin] with the provided [Level] configuration. #[inline] pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { let mut pin = Flex::new(pin); pin.set_as_output(); pin.set_level(initial_output); 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(); } /// Configure the logic inversion of this pin. /// /// Logic inversion applies to the input path of this pin. #[inline] pub fn set_inversion(&mut self, invert: bool) { self.pin.set_inversion(invert) } } /// 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> { pin: Flex<'d>, } impl<'d> OutputOpenDrain<'d> { /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level]. #[inline] pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { let mut pin = Flex::new(pin); pin.set_level(initial_output); pin.set_as_input_output(); 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() } /// Configure the logic inversion of this pin. /// /// Logic inversion applies to the input path of this pin. #[inline] pub fn set_inversion(&mut self, invert: bool) { self.pin.set_inversion(invert) } /// 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 } } /// Type-erased GPIO pin pub struct AnyPin { pub(crate) 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) -> Peri<'static, Self> { Peri::new_unchecked(Self { pin_port }) } } impl_peripheral!(AnyPin); impl Pin for AnyPin {} impl SealedPin for AnyPin { #[inline] fn pin_port(&self) -> u8 { self.pin_port } } /// 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: PeripheralType + Into + SealedPin + Sized + 'static { /// The index of this pin in PINCM (pin control management) registers. #[inline] fn pin_cm(&self) -> u8 { self._pin_cm() } } impl<'d> embedded_hal::digital::ErrorType for Flex<'d> { type Error = Infallible; } impl<'d> embedded_hal::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::digital::OutputPin for Flex<'d> { #[inline] fn set_low(&mut self) -> Result<(), Self::Error> { Ok(self.set_low()) } #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } } impl<'d> embedded_hal::digital::StatefulOutputPin for Flex<'d> { #[inline] fn is_set_high(&mut self) -> Result { Ok((*self).is_set_high()) } #[inline] 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::digital::ErrorType for Input<'d> { type Error = Infallible; } impl<'d> embedded_hal::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_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::digital::ErrorType for Output<'d> { type Error = Infallible; } impl<'d> embedded_hal::digital::OutputPin for Output<'d> { #[inline] fn set_low(&mut self) -> Result<(), Self::Error> { Ok(self.set_low()) } #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } } impl<'d> embedded_hal::digital::StatefulOutputPin for Output<'d> { #[inline] fn is_set_high(&mut self) -> Result { Ok((*self).is_set_high()) } #[inline] fn is_set_low(&mut self) -> Result { Ok((*self).is_set_low()) } } impl<'d> embedded_hal::digital::ErrorType for OutputOpenDrain<'d> { type Error = Infallible; } impl<'d> embedded_hal::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::digital::OutputPin for OutputOpenDrain<'d> { #[inline] fn set_low(&mut self) -> Result<(), Self::Error> { Ok(self.set_low()) } #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } } impl<'d> embedded_hal::digital::StatefulOutputPin for OutputOpenDrain<'d> { #[inline] fn is_set_high(&mut self) -> Result { Ok((*self).is_set_high()) } #[inline] fn is_set_low(&mut self) -> Result { Ok((*self).is_set_low()) } } 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(()) } } #[cfg_attr(mspm0g518x, allow(dead_code))] #[derive(Copy, Clone)] pub struct PfType { pull: Pull, input: bool, invert: bool, } impl PfType { pub const fn input(pull: Pull, invert: bool) -> Self { Self { pull, input: true, invert, } } pub const fn output(pull: Pull, invert: bool) -> Self { Self { pull, input: false, invert, } } } /// The pin function to disconnect peripherals from the pin. /// /// This is also the pin function used to connect to analog peripherals, such as an ADC. const DISCONNECT_PF: u8 = 0; /// The pin function for the GPIO peripheral. /// /// This is fixed to `1` for every part. const GPIO_PF: u8 = 1; macro_rules! impl_pin { ($name: ident, $port: expr, $pin_num: expr) => { impl crate::gpio::Pin for crate::peripherals::$name {} impl crate::gpio::SealedPin for crate::peripherals::$name { #[inline] fn pin_port(&self) -> u8 { ($port as u8) * 32 + $pin_num } } impl From for crate::gpio::AnyPin { fn from(val: crate::peripherals::$name) -> Self { Self { pin_port: crate::gpio::SealedPin::pin_port(&val), } } } }; } // TODO: Possible micro-op for C110X, not every pin is instantiated even on the 20 pin parts. // This would mean cfg guarding to just cfg guarding every pin instance. static PORTA_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; #[cfg(gpio_pb)] static PORTB_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; #[cfg(gpio_pc)] static PORTC_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; pub(crate) trait SealedPin { fn pin_port(&self) -> u8; fn port(&self) -> Port { match self.pin_port() / 32 { 0 => Port::PortA, #[cfg(gpio_pb)] 1 => Port::PortB, #[cfg(gpio_pc)] 2 => Port::PortC, _ => unreachable!(), } } fn waker(&self) -> &AtomicWaker { match self.port() { Port::PortA => &PORTA_WAKERS[self.bit_index()], #[cfg(gpio_pb)] Port::PortB => &PORTB_WAKERS[self.bit_index()], #[cfg(gpio_pc)] Port::PortC => &PORTC_WAKERS[self.bit_index()], } } fn _pin_cm(&self) -> u8 { // Some parts like the MSPM0L222x have pincm mappings all over the place. crate::gpio_pincm(self.pin_port()) } fn bit_index(&self) -> usize { (self.pin_port() % 32) as usize } #[inline] fn set_as_analog(&self) { let pincm = pac::IOMUX.pincm(self._pin_cm() as usize); pincm.modify(|w| { w.set_pf(DISCONNECT_PF); w.set_pipu(false); w.set_pipd(false); }); } #[cfg_attr(mspm0g518x, allow(dead_code))] fn update_pf(&self, ty: PfType) { let pincm = pac::IOMUX.pincm(self._pin_cm() as usize); let pf = pincm.read().pf(); set_pf(self._pin_cm() as usize, pf, ty); } #[cfg_attr(mspm0g518x, allow(dead_code))] fn set_as_pf(&self, pf: u8, ty: PfType) { set_pf(self._pin_cm() as usize, pf, ty) } /// 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] #[cfg_attr(mspm0g518x, allow(dead_code))] fn set_as_disconnected(&self) { self.set_as_analog(); } #[inline] fn block(&self) -> gpio::Gpio { match self.pin_port() / 32 { 0 => pac::GPIOA, #[cfg(gpio_pb)] 1 => pac::GPIOB, #[cfg(gpio_pc)] 2 => pac::GPIOC, _ => unreachable!(), } } } #[inline(never)] fn set_pf(pincm: usize, pf: u8, ty: PfType) { pac::IOMUX.pincm(pincm).modify(|w| { w.set_pf(pf); w.set_pc(true); w.set_pipu(ty.pull == Pull::Up); w.set_pipd(ty.pull == Pull::Down); w.set_inena(ty.input); w.set_inv(ty.invert); }); } #[must_use = "futures do nothing unless you `.await` or poll them"] struct InputFuture<'d> { pin: Peri<'d, AnyPin>, } impl<'d> InputFuture<'d> { fn new(pin: Peri<'d, AnyPin>, polarity: Polarity) -> Self { let block = pin.block(); // Before clearing any previous edge events, we must disable events. // // If we don't do this, it is possible that after we clear the interrupt, the current event // the hardware is listening for may not be the same event we will configure. This may result // in RIS being set. Then when interrupts are unmasked and RIS is set, we may get the wrong event // causing an interrupt. // // Selecting which polarity events happen is a RMW operation. critical_section::with(|_cs| { if pin.bit_index() >= 16 { block.polarity31_16().modify(|w| { w.set_dio(pin.bit_index() - 16, Polarity::DISABLE); }); } else { block.polarity15_0().modify(|w| { w.set_dio(pin.bit_index(), Polarity::DISABLE); }); }; }); // First clear the bit for this event. Otherwise previous edge events may be recorded. block.cpu_int().iclr().write(|w| { w.set_dio(pin.bit_index(), true); }); // Selecting which polarity events happen is a RMW operation. critical_section::with(|_cs| { // Tell the hardware which pin event we want to receive. if pin.bit_index() >= 16 { block.polarity31_16().modify(|w| { w.set_dio(pin.bit_index() - 16, polarity); }); } else { block.polarity15_0().modify(|w| { w.set_dio(pin.bit_index(), polarity); }); }; }); 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 = self.pin.waker(); waker.register(cx.waker()); // The interrupt handler will mask the interrupt if the event has occurred. if self.pin.block().cpu_int().ris().read().dio(self.pin.bit_index()) { return Poll::Ready(()); } // Unmasking the interrupt is a RMW operation. // // Guard with a critical section in case two different threads try to unmask at the same time. critical_section::with(|_cs| { self.pin.block().cpu_int().imask().modify(|w| { w.set_dio(self.pin.bit_index(), true); }); }); Poll::Pending } } pub(crate) fn init(gpio: gpio::Gpio) { gpio.gprcm().rstctl().write(|w| { w.set_resetstkyclr(true); w.set_resetassert(true); w.set_key(ResetKey::KEY); }); gpio.gprcm().pwren().write(|w| { w.set_enable(true); w.set_key(PwrenKey::KEY); }); gpio.evt_mode().modify(|w| { // The CPU will clear it's own interrupts w.set_cpu_cfg(EvtCfg::SOFTWARE); }); } #[cfg(feature = "rt")] fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { use crate::BitIter; // Only consider pins which have interrupts unmasked. let bits = gpio.cpu_int().mis().read().0; for i in BitIter(bits) { wakers[i as usize].wake(); // Notify the future that an edge event has occurred by masking the interrupt for this pin. gpio.cpu_int().imask().modify(|w| { w.set_dio(i as usize, false); }); } } #[cfg(all(gpioa_interrupt, gpioa_group))] compile_error!("gpioa_interrupt and gpioa_group are mutually exclusive cfgs"); #[cfg(all(gpiob_interrupt, gpiob_group))] compile_error!("gpiob_interrupt and gpiob_group are mutually exclusive cfgs"); // C110x and L110x have a dedicated interrupts just for GPIOA. // // These chips do not have a GROUP1 interrupt. #[cfg(all(feature = "rt", gpioa_interrupt))] #[interrupt] fn GPIOA() { irq_handler(pac::GPIOA, &PORTA_WAKERS); } #[cfg(all(feature = "rt", gpiob_interrupt))] #[interrupt] fn GPIOB() { irq_handler(pac::GPIOB, &PORTB_WAKERS); } // These symbols are weakly defined as DefaultHandler and are called by the interrupt group implementation. // // Defining these as no_mangle is required so that the linker will pick these over the default handler. #[cfg(all(feature = "rt", gpioa_group))] #[unsafe(no_mangle)] #[allow(non_snake_case)] fn GPIOA() { irq_handler(pac::GPIOA, &PORTA_WAKERS); } #[cfg(all(feature = "rt", gpiob_group))] #[unsafe(no_mangle)] #[allow(non_snake_case)] fn GPIOB() { irq_handler(pac::GPIOB, &PORTB_WAKERS); } #[cfg(all(feature = "rt", gpioc_group))] #[allow(non_snake_case)] #[unsafe(no_mangle)] fn GPIOC() { irq_handler(pac::GPIOC, &PORTC_WAKERS); } ================================================ FILE: embassy-mspm0/src/i2c.rs ================================================ #![macro_use] use core::future; use core::marker::PhantomData; use core::sync::atomic::{AtomicU32, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use mspm0_metapac::i2c; use crate::Peri; use crate::gpio::{AnyPin, PfType, Pull, SealedPin}; use crate::interrupt::typelevel::Binding; use crate::interrupt::{Interrupt, InterruptExt}; use crate::mode::{Async, Blocking, Mode}; use crate::pac::i2c::{I2c as Regs, vals}; use crate::pac::{self}; /// The clock source for the I2C. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClockSel { /// Use the bus clock. /// /// Configurable clock. BusClk, /// Use the middle frequency clock. /// /// The MCLK runs at 4 MHz. MfClk, } /// The clock divider for the I2C. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClockDiv { // "Do not divide clock source. DivBy1, // "Divide clock source by 2. DivBy2, // "Divide clock source by 3. DivBy3, // "Divide clock source by 4. DivBy4, // "Divide clock source by 5. DivBy5, // "Divide clock source by 6. DivBy6, // "Divide clock source by 7. DivBy7, // "Divide clock source by 8. DivBy8, } impl ClockDiv { pub(crate) fn into(self) -> vals::Ratio { match self { Self::DivBy1 => vals::Ratio::DIV_BY_1, Self::DivBy2 => vals::Ratio::DIV_BY_2, Self::DivBy3 => vals::Ratio::DIV_BY_3, Self::DivBy4 => vals::Ratio::DIV_BY_4, Self::DivBy5 => vals::Ratio::DIV_BY_5, Self::DivBy6 => vals::Ratio::DIV_BY_6, Self::DivBy7 => vals::Ratio::DIV_BY_7, Self::DivBy8 => vals::Ratio::DIV_BY_8, } } fn divider(self) -> u32 { match self { Self::DivBy1 => 1, Self::DivBy2 => 2, Self::DivBy3 => 3, Self::DivBy4 => 4, Self::DivBy5 => 5, Self::DivBy6 => 6, Self::DivBy7 => 7, Self::DivBy8 => 8, } } } /// The I2C mode. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BusSpeed { /// Standard mode. /// /// The Standard mode runs at 100 kHz. Standard, /// Fast mode. /// /// The fast mode runs at 400 kHz. FastMode, /// Fast mode plus. /// /// The fast mode plus runs at 1 MHz. FastModePlus, /// Custom mode. /// /// The custom mode frequency (in Hz) can be set manually. Custom(u32), } impl BusSpeed { fn hertz(self) -> u32 { match self { Self::Standard => 100_000, Self::FastMode => 400_000, Self::FastModePlus => 1_000_000, Self::Custom(s) => s, } } } #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Config Error pub enum ConfigError { /// Invalid clock rate. /// /// The clock rate could not be configured with the given conifguratoin. InvalidClockRate, /// Clock source not enabled. /// /// The clock soure is not enabled is SYSCTL. ClockSourceNotEnabled, /// Invalid target address. /// /// The target address is not 7-bit. InvalidTargetAddress, } #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Config pub struct Config { /// I2C clock source. pub(crate) clock_source: ClockSel, /// I2C clock divider. pub clock_div: ClockDiv, /// If true: invert SDA pin signal values (VDD = 0/mark, Gnd = 1/idle). pub invert_sda: bool, /// If true: invert SCL pin signal values (VDD = 0/mark, Gnd = 1/idle). pub invert_scl: bool, /// Set the pull configuration for the SDA pin. pub sda_pull: Pull, /// Set the pull configuration for the SCL pin. pub scl_pull: Pull, /// Set the pull configuration for the SCL pin. pub bus_speed: BusSpeed, } impl Default for Config { fn default() -> Self { Self { clock_source: ClockSel::MfClk, clock_div: ClockDiv::DivBy1, invert_sda: false, invert_scl: false, sda_pull: Pull::None, scl_pull: Pull::None, bus_speed: BusSpeed::Standard, } } } impl Config { pub fn sda_pf(&self) -> PfType { PfType::input(self.sda_pull, self.invert_sda) } pub fn scl_pf(&self) -> PfType { PfType::input(self.scl_pull, self.invert_scl) } fn calculate_timer_period(&self) -> u8 { // Sets the timer period to bring the clock frequency to the selected I2C speed // From the documentation: TPR = (I2C_CLK / (I2C_FREQ * (SCL_LP + SCL_HP))) - 1 where: // - I2C_FREQ is desired I2C frequency (= I2C_BASE_FREQ divided by I2C_DIV) // - TPR is the Timer Period register value (range of 1 to 127) // - SCL_LP is the SCL Low period (fixed at 6) // - SCL_HP is the SCL High period (fixed at 4) // - I2C_CLK is functional clock frequency return ((self.calculate_clock_source() / (self.bus_speed.hertz() * 10u32)) - 1) .try_into() .unwrap(); } #[cfg(any(mspm0c110x, mspm0c1105_c1106))] pub(crate) fn calculate_clock_source(&self) -> u32 { // Assume that BusClk has default value. // TODO: calculate BusClk more precisely. match self.clock_source { ClockSel::MfClk => 4_000_000 / self.clock_div.divider(), ClockSel::BusClk => 24_000_000 / self.clock_div.divider(), } } #[cfg(any( mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0h321x, mspm0l110x, mspm0l122x, mspm0l130x, mspm0l134x, mspm0l222x ))] pub(crate) fn calculate_clock_source(&self) -> u32 { // Assume that BusClk has default value. // TODO: calculate BusClk more precisely. match self.clock_source { ClockSel::MfClk => 4_000_000 / self.clock_div.divider(), ClockSel::BusClk => 32_000_000 / self.clock_div.divider(), } } fn check_clock_i2c(&self) -> bool { // make sure source clock is ~20 faster than i2c clock let clk_ratio = 20; let i2c_clk = self.bus_speed.hertz() / self.clock_div.divider(); let src_clk = self.calculate_clock_source(); // check clock rate return src_clk >= i2c_clk * clk_ratio; } fn define_clock_source(&mut self) -> bool { // decide which clock source to choose based on i2c clock. // If i2c speed <= 200kHz, use MfClk, otherwise use BusClk if self.bus_speed.hertz() / self.clock_div.divider() > 200_000 { // TODO: check if BUSCLK enabled self.clock_source = ClockSel::BusClk; } else { // is MFCLK enabled if !pac::SYSCTL.mclkcfg().read().usemftick() { return false; } self.clock_source = ClockSel::MfClk; } return true; } /// Check the config. /// /// Make sure that configuration is valid and enabled by the system. pub fn check_config(&mut self) -> Result<(), ConfigError> { if !self.define_clock_source() { return Err(ConfigError::ClockSourceNotEnabled); } if !self.check_clock_i2c() { return Err(ConfigError::InvalidClockRate); } Ok(()) } } /// Serial error #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] 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, /// Transfer length is over limit. TransferLengthIsOverLimit, } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let message = match self { Self::Bus => "Bus Error", Self::Arbitration => "Arbitration Lost", Self::Nack => "ACK Not Received", Self::Timeout => "Request Timed Out", Self::Crc => "CRC Mismatch", Self::Overrun => "Buffer Overrun", Self::ZeroLengthTransfer => "Zero-Length Transfers are not allowed", Self::TransferLengthIsOverLimit => "Transfer length is over limit", }; write!(f, "{}", message) } } impl core::error::Error for Error {} /// I2C Driver. pub struct I2c<'d, M: Mode> { info: &'static Info, state: &'static State, scl: Option>, sda: Option>, _phantom: PhantomData, } impl<'d, M: Mode> SetConfig for I2c<'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> I2c<'d, Blocking> { pub fn new_blocking( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, mut config: Config, ) -> Result { if let Err(err) = config.check_config() { return Err(err); } Self::new_inner(peri, scl, sda, config) } } impl<'d> I2c<'d, Async> { pub fn new_async( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, _irq: impl Binding> + 'd, mut config: Config, ) -> Result { if let Err(err) = config.check_config() { return Err(err); } let i2c = Self::new_inner(peri, scl, sda, config); T::info().interrupt.unpend(); unsafe { T::info().interrupt.enable() }; i2c } } impl<'d, M: Mode> I2c<'d, M> { /// Reconfigure the driver pub fn set_config(&mut self, mut config: Config) -> Result<(), ConfigError> { if let Err(err) = config.check_config() { return Err(err); } self.info.interrupt.disable(); if let Some(ref sda) = self.sda { sda.update_pf(config.sda_pf()); } if let Some(ref scl) = self.scl { scl.update_pf(config.scl_pf()); } self.init(&config) } fn init(&mut self, config: &Config) -> Result<(), ConfigError> { // Init I2C self.info.regs.clksel().write(|w| match config.clock_source { ClockSel::BusClk => { w.set_mfclk_sel(false); w.set_busclk_sel(true); } ClockSel::MfClk => { w.set_mfclk_sel(true); w.set_busclk_sel(false); } }); self.info.regs.clkdiv().write(|w| w.set_ratio(config.clock_div.into())); // set up glitch filter self.info.regs.gfctl().modify(|w| { w.set_agfen(false); w.set_agfsel(vals::Agfsel::AGLIT_50); w.set_chain(true); }); // Reset controller transfer, follow TI example self.info.regs.controller(0).cctr().modify(|w| { w.set_burstrun(false); w.set_start(false); w.set_stop(false); w.set_ack(false); w.set_cackoen(false); w.set_rd_on_txempty(false); w.set_cblen(0); }); self.state .clock .store(config.calculate_clock_source(), Ordering::Relaxed); self.info .regs .controller(0) .ctpr() .write(|w| w.set_tpr(config.calculate_timer_period())); // Set Tx Fifo threshold, follow TI example self.info .regs .controller(0) .cfifoctl() .write(|w| w.set_txtrig(vals::CfifoctlTxtrig::EMPTY)); // Set Rx Fifo threshold, follow TI example self.info .regs .controller(0) .cfifoctl() .write(|w| w.set_rxtrig(vals::CfifoctlRxtrig::LEVEL_1)); // Enable controller clock stretching, follow TI example self.info.regs.controller(0).ccr().modify(|w| { w.set_clkstretch(true); w.set_active(true); }); Ok(()) } fn master_stop(&mut self) { // not the first transaction, delay 1000 cycles cortex_m::asm::delay(1000); // Stop transaction self.info.regs.controller(0).cctr().modify(|w| { w.set_cblen(0); w.set_stop(true); w.set_start(false); }); } fn master_continue(&mut self, length: usize, send_ack_nack: bool, send_stop: bool) -> Result<(), Error> { // delay between ongoing transactions, 1000 cycles cortex_m::asm::delay(1000); // Update transaction to length amount of bytes self.info.regs.controller(0).cctr().modify(|w| { w.set_cblen(length as u16); w.set_start(false); w.set_ack(send_ack_nack); w.set_stop(send_stop); }); Ok(()) } fn master_read( &mut self, address: u8, length: usize, restart: bool, send_ack_nack: bool, send_stop: bool, ) -> Result<(), Error> { if restart { // not the first transaction, delay 1000 cycles cortex_m::asm::delay(1000); } // 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. self.info.regs.controller(0).csa().modify(|w| { w.set_taddr(address as u16); w.set_cmode(vals::Mode::MODE7); w.set_dir(vals::Dir::RECEIVE); }); self.info.regs.controller(0).cctr().modify(|w| { w.set_cblen(length as u16); w.set_burstrun(true); w.set_ack(send_ack_nack); w.set_start(true); w.set_stop(send_stop); }); Ok(()) } fn master_write(&mut self, address: u8, length: usize, send_stop: bool) -> Result<(), Error> { // Start transfer of length amount of bytes self.info.regs.controller(0).csa().modify(|w| { w.set_taddr(address as u16); w.set_cmode(vals::Mode::MODE7); w.set_dir(vals::Dir::TRANSMIT); }); self.info.regs.controller(0).cctr().modify(|w| { w.set_cblen(length as u16); w.set_burstrun(true); w.set_start(true); w.set_stop(send_stop); }); Ok(()) } fn check_error(&self) -> Result<(), Error> { let csr = self.info.regs.controller(0).csr().read(); if csr.err() { return Err(Error::Nack); } else if csr.arblst() { return Err(Error::Arbitration); } Ok(()) } } impl<'d> I2c<'d, Blocking> { fn master_blocking_continue(&mut self, length: usize, send_ack_nack: bool, send_stop: bool) -> Result<(), Error> { // Perform transaction self.master_continue(length, send_ack_nack, send_stop)?; // Poll until the Controller process all bytes or NACK while self.info.regs.controller(0).csr().read().busy() {} Ok(()) } fn master_blocking_read( &mut self, address: u8, length: usize, restart: bool, send_ack_nack: bool, send_stop: bool, ) -> Result<(), Error> { // unless restart, Wait for the controller to be idle, if !restart { while !self.info.regs.controller(0).csr().read().idle() {} } self.master_read(address, length, restart, send_ack_nack, send_stop)?; // Poll until the Controller process all bytes or NACK while self.info.regs.controller(0).csr().read().busy() {} Ok(()) } fn master_blocking_write(&mut self, address: u8, length: usize, send_stop: bool) -> Result<(), Error> { // Wait for the controller to be idle while !self.info.regs.controller(0).csr().read().idle() {} // Perform writing self.master_write(address, length, send_stop)?; // Poll until the Controller writes all bytes or NACK while self.info.regs.controller(0).csr().read().busy() {} Ok(()) } fn read_blocking_internal( &mut self, address: u8, read: &mut [u8], restart: bool, end_w_stop: bool, ) -> Result<(), Error> { if read.is_empty() { return Err(Error::ZeroLengthTransfer); } if read.len() > self.info.fifo_size { return Err(Error::TransferLengthIsOverLimit); } let read_len = read.len(); let mut bytes_to_read = read_len; for (number, chunk) in read.chunks_mut(self.info.fifo_size).enumerate() { bytes_to_read -= chunk.len(); // if the current transaction is the last & end_w_stop, send stop let send_stop = bytes_to_read == 0 && end_w_stop; // if there are still bytes to read, send ACK let send_ack_nack = bytes_to_read != 0; if number == 0 { self.master_blocking_read( address, chunk.len().min(self.info.fifo_size), restart, send_ack_nack, send_stop, )? } else { self.master_blocking_continue(chunk.len(), send_ack_nack, send_stop)?; } // check errors if let Err(err) = self.check_error() { self.master_stop(); return Err(err); } for byte in chunk { *byte = self.info.regs.controller(0).crxdata().read().value(); } } Ok(()) } fn write_blocking_internal(&mut self, address: u8, write: &[u8], end_w_stop: bool) -> Result<(), Error> { if write.is_empty() { return Err(Error::ZeroLengthTransfer); } if write.len() > self.info.fifo_size { return Err(Error::TransferLengthIsOverLimit); } let mut bytes_to_send = write.len(); for (number, chunk) in write.chunks(self.info.fifo_size).enumerate() { for byte in chunk { let ctrl0 = self.info.regs.controller(0).ctxdata(); ctrl0.write(|w| w.set_value(*byte)); } // if the current transaction is the last & end_w_stop, send stop bytes_to_send -= chunk.len(); let send_stop = end_w_stop && bytes_to_send == 0; if number == 0 { self.master_blocking_write(address, chunk.len(), send_stop)?; } else { self.master_blocking_continue(chunk.len(), false, send_stop)?; } // check errors if let Err(err) = self.check_error() { self.master_stop(); return Err(err); } } Ok(()) } // ========================= // Blocking public API /// Blocking read. pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} self.read_blocking_internal(address, read, false, true) } /// Blocking write. pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} self.write_blocking_internal(address, write, true) } /// Blocking write, restart, read. pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} let err = self.write_blocking_internal(address, write, false); if err != Ok(()) { return err; } self.read_blocking_internal(address, read, true, true) } } impl<'d> I2c<'d, Async> { async fn write_async_internal(&mut self, addr: u8, write: &[u8], end_w_stop: bool) -> Result<(), Error> { let ctrl = self.info.regs.controller(0); let mut bytes_to_send = write.len(); for (number, chunk) in write.chunks(self.info.fifo_size).enumerate() { self.info.regs.cpu_int(0).imask().modify(|w| { w.set_carblost(true); w.set_cnack(true); w.set_ctxdone(true); }); for byte in chunk { ctrl.ctxdata().write(|w| w.set_value(*byte)); } // if the current transaction is the last & end_w_stop, send stop bytes_to_send -= chunk.len(); let send_stop = end_w_stop && bytes_to_send == 0; if number == 0 { self.master_write(addr, chunk.len(), send_stop)?; } else { self.master_continue(chunk.len(), false, send_stop)?; } let res: Result<(), Error> = future::poll_fn(|cx| { use crate::i2c::vals::CpuIntIidxStat; // Register prior to checking the condition self.state.waker.register(cx.waker()); let result = match self.info.regs.cpu_int(0).iidx().read().stat() { CpuIntIidxStat::NO_INTR => Poll::Pending, CpuIntIidxStat::CNACKFG => Poll::Ready(Err(Error::Nack)), CpuIntIidxStat::CARBLOSTFG => Poll::Ready(Err(Error::Arbitration)), CpuIntIidxStat::CTXDONEFG => Poll::Ready(Ok(())), _ => Poll::Pending, }; if !result.is_pending() { self.info .regs .cpu_int(0) .imask() .write_value(i2c::regs::CpuInt::default()); } return result; }) .await; if res.is_err() { self.master_stop(); return res; } } Ok(()) } async fn read_async_internal( &mut self, addr: u8, read: &mut [u8], restart: bool, end_w_stop: bool, ) -> Result<(), Error> { let read_len = read.len(); let mut bytes_to_read = read_len; for (number, chunk) in read.chunks_mut(self.info.fifo_size).enumerate() { bytes_to_read -= chunk.len(); // if the current transaction is the last & end_w_stop, send stop let send_stop = bytes_to_read == 0 && end_w_stop; // if there are still bytes to read, send ACK let send_ack_nack = bytes_to_read != 0; self.info.regs.cpu_int(0).imask().modify(|w| { w.set_carblost(true); w.set_cnack(true); w.set_crxdone(true); }); if number == 0 { self.master_read(addr, chunk.len(), restart, send_ack_nack, send_stop)? } else { self.master_continue(chunk.len(), send_ack_nack, send_stop)?; } let res: Result<(), Error> = future::poll_fn(|cx| { use crate::i2c::vals::CpuIntIidxStat; // Register prior to checking the condition self.state.waker.register(cx.waker()); let result = match self.info.regs.cpu_int(0).iidx().read().stat() { CpuIntIidxStat::NO_INTR => Poll::Pending, CpuIntIidxStat::CNACKFG => Poll::Ready(Err(Error::Nack)), CpuIntIidxStat::CARBLOSTFG => Poll::Ready(Err(Error::Arbitration)), CpuIntIidxStat::CRXDONEFG => Poll::Ready(Ok(())), _ => Poll::Pending, }; if !result.is_pending() { self.info .regs .cpu_int(0) .imask() .write_value(i2c::regs::CpuInt::default()); } return result; }) .await; if res.is_err() { self.master_stop(); return res; } for byte in chunk { *byte = self.info.regs.controller(0).crxdata().read().value(); } } Ok(()) } // ========================= // Async public API pub async fn async_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} self.write_async_internal(address, write, true).await } pub async fn async_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} self.read_async_internal(address, read, false, true).await } pub async fn async_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} let err = self.write_async_internal(address, write, false).await; if err != Ok(()) { return err; } self.read_async_internal(address, read, true, true).await } } impl<'d> embedded_hal_02::blocking::i2c::Read for I2c<'d, Blocking> { type Error = Error; fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { self.blocking_read(address, buffer) } } impl<'d> embedded_hal_02::blocking::i2c::Write for I2c<'d, Blocking> { type Error = Error; fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { self.blocking_write(address, bytes) } } impl<'d> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, Blocking> { 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> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, Blocking> { type Error = Error; fn exec( &mut self, address: u8, operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>], ) -> Result<(), Self::Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} for i in 0..operations.len() { match &mut operations[i] { embedded_hal_02::blocking::i2c::Operation::Read(buf) => { self.read_blocking_internal(address, buf, false, false)? } embedded_hal_02::blocking::i2c::Operation::Write(buf) => { self.write_blocking_internal(address, buf, false)? } } } self.master_stop(); Ok(()) } } impl embedded_hal::i2c::Error for Error { fn kind(&self) -> embedded_hal::i2c::ErrorKind { match *self { Self::Bus => embedded_hal::i2c::ErrorKind::Bus, Self::Arbitration => embedded_hal::i2c::ErrorKind::ArbitrationLoss, Self::Nack => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Unknown), Self::Timeout => embedded_hal::i2c::ErrorKind::Other, Self::Crc => embedded_hal::i2c::ErrorKind::Other, Self::Overrun => embedded_hal::i2c::ErrorKind::Overrun, Self::ZeroLengthTransfer => embedded_hal::i2c::ErrorKind::Other, Self::TransferLengthIsOverLimit => embedded_hal::i2c::ErrorKind::Other, } } } impl<'d, M: Mode> embedded_hal::i2c::ErrorType for I2c<'d, M> { type Error = Error; } impl<'d> embedded_hal::i2c::I2c for I2c<'d, Blocking> { 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::i2c::Operation<'_>], ) -> Result<(), Self::Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} for i in 0..operations.len() { match &mut operations[i] { embedded_hal::i2c::Operation::Read(buf) => self.read_blocking_internal(address, buf, false, false)?, embedded_hal::i2c::Operation::Write(buf) => self.write_blocking_internal(address, buf, false)?, } } self.master_stop(); Ok(()) } } 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.async_read(address, read).await } async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { self.async_write(address, write).await } async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { self.async_write_read(address, write, read).await } async fn transaction( &mut self, address: u8, operations: &mut [embedded_hal::i2c::Operation<'_>], ) -> Result<(), Self::Error> { // wait until bus is free while self.info.regs.controller(0).csr().read().busbsy() {} for i in 0..operations.len() { match &mut operations[i] { embedded_hal::i2c::Operation::Read(buf) => self.read_async_internal(address, buf, false, false).await?, embedded_hal::i2c::Operation::Write(buf) => self.write_async_internal(address, buf, false).await?, } } self.master_stop(); Ok(()) } } /// Interrupt handler. pub struct InterruptHandler { _i2c: PhantomData, } impl crate::interrupt::typelevel::Handler for InterruptHandler { // Mask interrupts and wake any task waiting for this interrupt unsafe fn on_interrupt() { T::state().waker.wake(); } } /// Peripheral instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType { type Interrupt: crate::interrupt::typelevel::Interrupt; } /// I2C `SDA` pin trait pub trait SdaPin: crate::gpio::Pin { /// Get the PF number needed to use this pin as `SDA`. fn pf_num(&self) -> u8; } /// I2C `SCL` pin trait pub trait SclPin: crate::gpio::Pin { /// Get the PF number needed to use this pin as `SCL`. fn pf_num(&self) -> u8; } // ==== IMPL types ==== pub(crate) struct Info { pub(crate) regs: Regs, pub(crate) interrupt: Interrupt, pub fifo_size: usize, } pub(crate) struct State { /// The clock rate of the I2C. This might be configured. pub(crate) clock: AtomicU32, pub(crate) waker: AtomicWaker, } impl<'d, M: Mode> I2c<'d, M> { fn new_inner( _peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: Config, ) -> Result { // Init power for I2C T::info().regs.gprcm(0).rstctl().write(|w| { w.set_resetstkyclr(true); w.set_resetassert(true); w.set_key(vals::ResetKey::KEY); }); T::info().regs.gprcm(0).pwren().write(|w| { w.set_enable(true); w.set_key(vals::PwrenKey::KEY); }); // init delay, 16 cycles cortex_m::asm::delay(16); // Init GPIO let scl_inner = new_pin!(scl, config.scl_pf()); let sda_inner = new_pin!(sda, config.sda_pf()); if let Some(ref scl) = scl_inner { let pincm = pac::IOMUX.pincm(scl._pin_cm() as usize); pincm.modify(|w| { w.set_hiz1(true); }); } if let Some(ref sda) = sda_inner { let pincm = pac::IOMUX.pincm(sda._pin_cm() as usize); pincm.modify(|w| { w.set_hiz1(true); }); } let mut this = Self { info: T::info(), state: T::state(), scl: scl_inner, sda: sda_inner, _phantom: PhantomData, }; this.init(&config)?; Ok(this) } } pub(crate) trait SealedInstance { fn info() -> &'static Info; fn state() -> &'static State; } macro_rules! impl_i2c_instance { ($instance: ident, $fifo_size: expr) => { impl crate::i2c::SealedInstance for crate::peripherals::$instance { fn info() -> &'static crate::i2c::Info { use crate::i2c::Info; use crate::interrupt::typelevel::Interrupt; const INFO: Info = Info { regs: crate::pac::$instance, interrupt: crate::interrupt::typelevel::$instance::IRQ, fifo_size: $fifo_size, }; &INFO } fn state() -> &'static crate::i2c::State { use crate::i2c::State; use crate::interrupt::typelevel::Interrupt; static STATE: State = State { clock: core::sync::atomic::AtomicU32::new(0), waker: embassy_sync::waitqueue::AtomicWaker::new(), }; &STATE } } impl crate::i2c::Instance for crate::peripherals::$instance { type Interrupt = crate::interrupt::typelevel::$instance; } }; } macro_rules! impl_i2c_sda_pin { ($instance: ident, $pin: ident, $pf: expr) => { impl crate::i2c::SdaPin for crate::peripherals::$pin { fn pf_num(&self) -> u8 { $pf } } }; } macro_rules! impl_i2c_scl_pin { ($instance: ident, $pin: ident, $pf: expr) => { impl crate::i2c::SclPin for crate::peripherals::$pin { fn pf_num(&self) -> u8 { $pf } } }; } #[cfg(test)] mod tests { use crate::i2c::{BusSpeed, ClockDiv, ClockSel, Config}; /// These tests are based on TI's reference caluclation. #[test] fn ti_calculate_timer_period() { let mut config = Config::default(); config.clock_div = ClockDiv::DivBy1; config.bus_speed = BusSpeed::FastMode; config.clock_source = ClockSel::BusClk; assert_eq!(config.calculate_timer_period(), 7u8); } #[test] fn ti_calculate_timer_period_2() { let mut config = Config::default(); config.clock_div = ClockDiv::DivBy2; config.bus_speed = BusSpeed::FastMode; config.clock_source = ClockSel::BusClk; assert_eq!(config.calculate_timer_period(), 3u8); } #[test] fn ti_calculate_timer_period_3() { let mut config = Config::default(); config.clock_div = ClockDiv::DivBy2; config.bus_speed = BusSpeed::Standard; config.clock_source = ClockSel::BusClk; assert_eq!(config.calculate_timer_period(), 15u8); } #[test] fn ti_calculate_timer_period_4() { let mut config = Config::default(); config.clock_div = ClockDiv::DivBy2; config.bus_speed = BusSpeed::Custom(100_000); config.clock_source = ClockSel::BusClk; assert_eq!(config.calculate_timer_period(), 15u8); } #[test] fn clock_check_fastmodeplus_rate_with_busclk() { let mut config = Config::default(); config.clock_source = ClockSel::BusClk; config.bus_speed = BusSpeed::FastModePlus; assert!(config.check_clock_i2c()); } #[test] fn clock_check_fastmode_rate_with_busclk() { let mut config = Config::default(); config.clock_source = ClockSel::BusClk; config.bus_speed = BusSpeed::FastMode; assert!(config.check_clock_i2c()); } #[test] fn clock_check_fastmodeplus_rate_with_mfclk() { let mut config = Config::default(); config.clock_source = ClockSel::MfClk; config.bus_speed = BusSpeed::FastModePlus; assert!(!config.check_clock_i2c()); } #[test] fn clock_check_fastmode_rate_with_mfclk() { let mut config = Config::default(); config.clock_source = ClockSel::MfClk; config.bus_speed = BusSpeed::FastMode; assert!(!config.check_clock_i2c()); } } ================================================ FILE: embassy-mspm0/src/i2c_target.rs ================================================ //! Inter-Integrated-Circuit (I2C) Target // The following code is modified from embassy-stm32 and embassy-rp // https://github.com/embassy-rs/embassy/tree/main/embassy-stm32 // https://github.com/embassy-rs/embassy/tree/main/embassy-rp use core::future::poll_fn; use core::marker::PhantomData; use core::sync::atomic::Ordering; use core::task::Poll; use embassy_embedded_hal::SetConfig; use mspm0_metapac::i2c::vals::CpuIntIidxStat; use crate::gpio::{AnyPin, SealedPin}; // Re-use I2c controller types use crate::i2c::{ClockSel, ConfigError, Info, Instance, InterruptHandler, SclPin, SdaPin, State}; use crate::interrupt::InterruptExt; use crate::mode::{Async, Blocking, Mode}; use crate::pac::i2c::vals; use crate::pac::{self}; use crate::{Peri, i2c, i2c_target, interrupt}; #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Config pub struct Config { /// 7-bit Target Address pub target_addr: u8, /// Control if the target should ack to and report general calls. pub general_call: bool, } impl Default for Config { fn default() -> Self { Self { target_addr: 0x48, general_call: false, } } } /// I2C error #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { /// 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 from the controller. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Command { /// General Call Write: Controller sent the General Call address (0x00) followed by data. /// Contains the number of bytes written by the controller. GeneralCall(usize), /// Read: Controller wants to read data from the target. Read, /// Write: Controller sent the target's address followed by data. /// Contains the number of bytes written by the controller. Write(usize), /// Write followed by Read (Repeated Start): Controller wrote data, then issued a repeated /// start and wants to read data. Contains the number of bytes written before the read. WriteRead(usize), } /// Status after responding to a controller read request. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadStatus { /// Transaction completed successfully. The controller either NACKed the last byte /// or sent a STOP condition. Done, /// Transaction incomplete, controller trying to read more bytes than were provided NeedMoreBytes, /// Transaction complete, but controller stopped reading bytes before we ran out LeftoverBytes(u16), } /// I2C Target driver. // Use the same Instance, SclPin, SdaPin traits as the controller pub struct I2cTarget<'d, M: Mode> { info: &'static Info, state: &'static State, scl: Option>, sda: Option>, config: i2c::Config, target_config: i2c_target::Config, _phantom: PhantomData, } impl<'d> SetConfig for I2cTarget<'d, Async> { type Config = (i2c::Config, i2c_target::Config); type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.info.interrupt.disable(); if let Some(ref sda) = self.sda { sda.update_pf(config.0.sda_pf()); } if let Some(ref scl) = self.scl { scl.update_pf(config.0.scl_pf()); } self.config = config.0.clone(); self.target_config = config.1.clone(); self.reset() } } impl<'d> SetConfig for I2cTarget<'d, Blocking> { type Config = (i2c::Config, i2c_target::Config); type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { if let Some(ref sda) = self.sda { sda.update_pf(config.0.sda_pf()); } if let Some(ref scl) = self.scl { scl.update_pf(config.0.scl_pf()); } self.config = config.0.clone(); self.target_config = config.1.clone(); self.reset() } } impl<'d> I2cTarget<'d, Async> { /// Create a new asynchronous I2C target driver using interrupts /// The `config` reuses the i2c controller config to setup the clock while `target_config` /// configures i2c target specific parameters. pub fn new( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: i2c::Config, target_config: i2c_target::Config, ) -> Result { let mut this = Self::new_inner( peri, new_pin!(scl, config.scl_pf()), new_pin!(sda, config.sda_pf()), config, target_config, ); this.reset()?; Ok(this) } /// 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 controller. pub fn reset(&mut self) -> Result<(), ConfigError> { self.init()?; unsafe { self.info.interrupt.enable() }; Ok(()) } } impl<'d> I2cTarget<'d, Blocking> { /// Create a new blocking I2C target driver. /// The `config` reuses the i2c controller config to setup the clock while `target_config` /// configures i2c target specific parameters. pub fn new_blocking( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, config: i2c::Config, target_config: i2c_target::Config, ) -> Result { let mut this = Self::new_inner( peri, new_pin!(scl, config.scl_pf()), new_pin!(sda, config.sda_pf()), config, target_config, ); this.reset()?; Ok(this) } /// 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 controller. pub fn reset(&mut self) -> Result<(), ConfigError> { self.init()?; Ok(()) } } impl<'d, M: Mode> I2cTarget<'d, M> { fn new_inner( _peri: Peri<'d, T>, scl: Option>, sda: Option>, config: i2c::Config, target_config: i2c_target::Config, ) -> Self { if let Some(ref scl) = scl { let pincm = pac::IOMUX.pincm(scl._pin_cm() as usize); pincm.modify(|w| { w.set_hiz1(true); }); } if let Some(ref sda) = sda { let pincm = pac::IOMUX.pincm(sda._pin_cm() as usize); pincm.modify(|w| { w.set_hiz1(true); }); } Self { info: T::info(), state: T::state(), scl, sda, config, target_config, _phantom: PhantomData, } } fn init(&mut self) -> Result<(), ConfigError> { let mut config = self.config; let target_config = self.target_config; let regs = self.info.regs; config.check_config()?; // Target address must be 7-bit if !(target_config.target_addr < 0x80) { return Err(ConfigError::InvalidTargetAddress); } regs.target(0).tctr().modify(|w| { w.set_active(false); }); // Init power for I2C regs.gprcm(0).rstctl().write(|w| { w.set_resetstkyclr(true); w.set_resetassert(true); w.set_key(vals::ResetKey::KEY); }); regs.gprcm(0).pwren().write(|w| { w.set_enable(true); w.set_key(vals::PwrenKey::KEY); }); self.info.interrupt.disable(); // Init delay from the M0 examples by TI in CCStudio (16 cycles) cortex_m::asm::delay(16); // Select and configure the I2C clock using the CLKSEL and CLKDIV registers regs.clksel().write(|w| match config.clock_source { ClockSel::BusClk => { w.set_mfclk_sel(false); w.set_busclk_sel(true); } ClockSel::MfClk => { w.set_mfclk_sel(true); w.set_busclk_sel(false); } }); regs.clkdiv().write(|w| w.set_ratio(config.clock_div.into())); // Configure at least one target address by writing the 7-bit address to I2Cx.SOAR register. The additional // target address can be enabled and configured by using I2Cx.TOAR2 register. regs.target(0).toar().modify(|w| { w.set_oaren(true); w.set_oar(target_config.target_addr as u16); }); self.state .clock .store(config.calculate_clock_source(), Ordering::Relaxed); regs.target(0).tctr().modify(|w| { w.set_gencall(target_config.general_call); w.set_tclkstretch(true); // Disable target wakeup, follow TI example. (TI note: Workaround for errata I2C_ERR_04.) w.set_twuen(false); w.set_txempty_on_treq(true); }); // Enable the I2C target mode by setting the ACTIVE bit in I2Cx.TCTR register. regs.target(0).tctr().modify(|w| { w.set_active(true); }); Ok(()) } #[inline(always)] fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) { let regs = self.info.regs; for b in &mut buffer[*offset..] { if regs.target(0).tfifosr().read().rxfifocnt() == 0 { break; } *b = regs.target(0).trxdata().read().value(); *offset += 1; } } /// Blocking function to empty the tx fifo /// /// This function can be used to empty the transmit FIFO if data remains after handling a 'read' command (LeftoverBytes). pub fn flush_tx_fifo(&mut self) { self.info.regs.target(0).tfifoctl().modify(|w| { w.set_txflush(true); }); while self.info.regs.target(0).tfifosr().read().txfifocnt() as usize != self.info.fifo_size {} self.info.regs.target(0).tfifoctl().modify(|w| { w.set_txflush(false); }); } } impl<'d> I2cTarget<'d, Async> { /// Wait asynchronously for commands from an I2C controller. /// `buffer` is provided in case controller does a 'write', 'write read', or 'general call' and is unused for 'read'. pub async fn listen(&mut self, buffer: &mut [u8]) -> Result { let regs = self.info.regs; let mut len = 0; // Set the rx fifo interrupt to avoid a fifo overflow regs.target(0).tfifoctl().modify(|r| { r.set_rxtrig(vals::TfifoctlRxtrig::LEVEL_6); }); self.wait_on( |me| { // Check if address matches the General Call address (0x00) let is_gencall = regs.target(0).tsr().read().addrmatch() == 0; if regs.target(0).tfifosr().read().rxfifocnt() > 0 { me.drain_fifo(buffer, &mut len); } if buffer.len() == len && regs.target(0).tfifosr().read().rxfifocnt() > 0 { if is_gencall { return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len()))); } else { return Poll::Ready(Err(Error::PartialWrite(buffer.len()))); } } let iidx = regs.cpu_int(0).iidx().read().stat(); trace!("ls:{} len:{}", iidx as u8, len); let result = match iidx { CpuIntIidxStat::TTXEMPTY => match len { 0 => Poll::Ready(Ok(Command::Read)), w => Poll::Ready(Ok(Command::WriteRead(w))), }, CpuIntIidxStat::TSTOPFG => match (is_gencall, len) { (_, 0) => Poll::Pending, (true, w) => Poll::Ready(Ok(Command::GeneralCall(w))), (false, w) => Poll::Ready(Ok(Command::Write(w))), }, _ => Poll::Pending, }; if !result.is_pending() { regs.cpu_int(0).imask().write(|_| {}); } result }, |_me| { regs.cpu_int(0).imask().write(|_| {}); regs.cpu_int(0).imask().modify(|w| { w.set_tgencall(true); w.set_trxfifotrg(true); w.set_tstop(true); w.set_ttxempty(true); }); }, ) .await } /// Respond to an I2C controller 'read' command, asynchronously. pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result { if buffer.is_empty() { return Err(Error::InvalidResponseBufferLength); } let regs = self.info.regs; let fifo_size = self.info.fifo_size; let mut chunks = buffer.chunks(self.info.fifo_size); self.wait_on( |_me| { if let Some(chunk) = chunks.next() { for byte in chunk { regs.target(0).ttxdata().write(|w| w.set_value(*byte)); } return Poll::Pending; } let iidx = regs.cpu_int(0).iidx().read().stat(); let fifo_bytes = fifo_size - regs.target(0).tfifosr().read().txfifocnt() as usize; trace!("rs:{}, fifo:{}", iidx as u8, fifo_bytes); let result = match iidx { CpuIntIidxStat::TTXEMPTY => Poll::Ready(Ok(ReadStatus::NeedMoreBytes)), CpuIntIidxStat::TSTOPFG => match fifo_bytes { 0 => Poll::Ready(Ok(ReadStatus::Done)), w => Poll::Ready(Ok(ReadStatus::LeftoverBytes(w as u16))), }, _ => Poll::Pending, }; if !result.is_pending() { regs.cpu_int(0).imask().write(|_| {}); } result }, |_me| { regs.cpu_int(0).imask().write(|_| {}); regs.cpu_int(0).imask().modify(|w| { w.set_ttxempty(true); w.set_tstop(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> { // The buffer size could be increased to reduce interrupt noise but has higher probability // of LeftoverBytes let buff = [fill]; loop { match self.respond_to_read(&buff).await { Ok(ReadStatus::NeedMoreBytes) => (), Ok(_) => break Ok(()), Err(e) => break Err(e), } } } /// Respond to a controller 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) } } /// Calls `f` to check if we are ready or not. /// If not, `g` is called once(to eg enable the required interrupts). /// The waker will always be registered prior to calling `f`. #[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), { poll_fn(|cx| { // Register prior to checking the condition self.state.waker.register(cx.waker()); let r = f(self); if r.is_pending() { g(self); } r }) .await } } impl<'d, M: Mode> Drop for I2cTarget<'d, M> { fn drop(&mut self) { // Ensure peripheral is disabled and pins are reset self.info.regs.target(0).tctr().modify(|w| w.set_active(false)); self.scl.as_ref().map(|x| x.set_as_disconnected()); self.sda.as_ref().map(|x| x.set_as_disconnected()); } } ================================================ FILE: embassy-mspm0/src/lib.rs ================================================ #![no_std] #![allow(unsafe_op_in_unsafe_fn)] // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] #![cfg_attr( docsrs, doc = "

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

The documentation here on `docs.rs` is built for a single chip only, 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")] // These mods MUST go first, so that the others see the macros. pub(crate) mod fmt; mod macros; pub mod adc; pub mod dma; pub mod gpio; // TODO: I2C unicomm #[cfg(not(unicomm))] pub mod i2c; #[cfg(not(unicomm))] pub mod i2c_target; #[cfg(any(mspm0g150x, mspm0g151x, mspm0g350x, mspm0g351x))] pub mod mathacl; pub mod timer; #[cfg(any(mspm0g150x, mspm0g151x, mspm0g350x, mspm0g351x, mspm0l122x, mspm0l222x))] pub mod trng; // TODO: UART unicomm #[cfg(not(unicomm))] pub mod uart; pub mod wwdt; /// Operating modes for peripherals. pub mod mode { trait SealedMode {} /// Operating mode for a peripheral. #[allow(private_bounds)] pub trait Mode: SealedMode {} /// Blocking mode. pub struct Blocking; impl SealedMode for Blocking {} impl Mode for Blocking {} /// Async mode. pub struct Async; impl SealedMode for Async {} impl Mode for Async {} } #[cfg(feature = "_time-driver")] mod time_driver; pub(crate) mod _generated { #![allow(dead_code)] #![allow(unused_imports)] #![allow(non_snake_case)] #![allow(missing_docs)] include!(concat!(env!("OUT_DIR"), "/_generated.rs")); } // Reexports pub(crate) use _generated::gpio_pincm; pub use _generated::{Peripherals, peripherals}; pub use embassy_hal_internal::Peri; #[cfg(feature = "unstable-pac")] pub use mspm0_metapac as pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use mspm0_metapac as pac; 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_nrf::{bind_interrupts, spim, peripherals}; /// /// bind_interrupts!( /// /// Binds the SPIM3 interrupt. /// 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 { ($(#[$attr:meta])* $vis:vis struct $name:ident { $( $(#[cfg($cond_irq:meta)])? $irq:ident => $( $(#[cfg($cond_handler:meta)])? $handler:ty ),*; )* }) => { #[derive(Copy, Clone)] $(#[$attr])* $vis struct $name; $( #[allow(non_snake_case)] #[unsafe(no_mangle)] $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { unsafe { $( $(#[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)* } } /// `embassy-mspm0` global configuration. #[non_exhaustive] #[derive(Clone, Copy)] pub struct Config { // TODO: OSC configuration. /// The size of DMA block transfer burst. /// /// If this is set to a value pub dma_burst_size: dma::BurstSize, /// Whether the DMA channels are used in a fixed priority or a round robin fashion. /// /// If [`false`], the DMA priorities are fixed. /// /// If [`true`], after a channel finishes a transfer it becomes the lowest priority. pub dma_round_robin: bool, } impl Default for Config { fn default() -> Self { Self { dma_burst_size: dma::BurstSize::Complete, dma_round_robin: false, } } } pub fn init(config: Config) -> Peripherals { critical_section::with(|cs| { let peripherals = Peripherals::take_with_cs(cs); // TODO: Further clock configuration pac::SYSCTL.mclkcfg().modify(|w| { // Enable MFCLK w.set_usemftick(true); // MDIV must be disabled if MFCLK is enabled. w.set_mdiv(0); }); // Enable MFCLK for peripheral use // // TODO: Optional? pac::SYSCTL.genclken().modify(|w| { w.set_mfpclken(true); }); pac::SYSCTL.borthreshold().modify(|w| { w.set_level(0); }); gpio::init(pac::GPIOA); #[cfg(gpio_pb)] gpio::init(pac::GPIOB); #[cfg(gpio_pc)] gpio::init(pac::GPIOC); _generated::enable_group_interrupts(cs); #[cfg(any(mspm0c110x, mspm0l110x))] unsafe { use crate::_generated::interrupt::typelevel::Interrupt; crate::interrupt::typelevel::GPIOA::enable(); } // SAFETY: Peripherals::take_with_cs will only be run once or panic. unsafe { dma::init(cs, config.dma_burst_size, config.dma_round_robin) }; #[cfg(feature = "_time-driver")] time_driver::init(cs); peripherals }) } pub(crate) mod sealed { #[allow(dead_code)] pub trait Sealed {} } 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) } } } } /// Reset cause values from SYSCTL.RSTCAUSE register. /// Based on MSPM0 L-series Technical Reference Manual Table 2-9 and /// MSPM0 G-series Technical Reference Manual Table 2-12. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ResetCause { /// No reset since last read NoReset, /// VDD < POR- violation, PMU trim parity fault, or SHUTDNSTOREx parity fault PorHwFailure, /// NRST pin reset (>1s) PorExternalNrst, /// Software-triggered POR PorSwTriggered, /// VDD < BOR- violation BorSupplyFailure, /// Wake from SHUTDOWN BorWakeFromShutdown, /// Non-PMU trim parity fault #[cfg(not(any( mspm0c110x, mspm0c1105_c1106, mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x )))] BootrstNonPmuParityFault, /// Fatal clock fault BootrstClockFault, /// Software-triggered BOOTRST BootrstSwTriggered, /// NRST pin reset (<1s) BootrstExternalNrst, /// WWDT0 violation BootrstWwdt0Violation, /// WWDT1 violation (G-series only) #[cfg(any(mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0g518x))] SysrstWwdt1Violation, /// BSL exit (if present) SysrstBslExit, /// BSL entry (if present) SysrstBslEntry, /// Uncorrectable flash ECC error (if present) #[cfg(not(any(mspm0c110x, mspm0c1105_c1106, mspm0g351x, mspm0g151x)))] SysrstFlashEccError, /// CPU lockup violation SysrstCpuLockupViolation, /// Debug-triggered SYSRST SysrstDebugTriggered, /// Software-triggered SYSRST SysrstSwTriggered, /// Debug-triggered CPURST CpurstDebugTriggered, /// Software-triggered CPURST CpurstSwTriggered, } /// Read the reset cause from the SYSCTL.RSTCAUSE register. /// /// This function reads the reset cause register which indicates why the last /// system reset occurred. The register is automatically cleared after being read, /// so this should be called only once per application startup. /// /// If the reset cause is not recognized, an `Err` containing the raw value is returned. #[must_use = "Reading reset cause will clear it"] pub fn read_reset_cause() -> Result { let cause_raw = pac::SYSCTL.rstcause().read().id(); use ResetCause::*; use pac::sysctl::vals::Id; match cause_raw { Id::NORST => Ok(NoReset), Id::PORHWFAIL => Ok(PorHwFailure), Id::POREXNRST => Ok(PorExternalNrst), Id::PORSW => Ok(PorSwTriggered), Id::BORSUPPLY => Ok(BorSupplyFailure), Id::BORWAKESHUTDN => Ok(BorWakeFromShutdown), #[cfg(not(any( mspm0c110x, mspm0c1105_c1106, mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0g518x, )))] Id::BOOTNONPMUPARITY => Ok(BootrstNonPmuParityFault), Id::BOOTCLKFAIL => Ok(BootrstClockFault), Id::BOOTSW => Ok(BootrstSwTriggered), Id::BOOTEXNRST => Ok(BootrstExternalNrst), Id::BOOTWWDT0 => Ok(BootrstWwdt0Violation), Id::SYSBSLEXIT => Ok(SysrstBslExit), Id::SYSBSLENTRY => Ok(SysrstBslEntry), #[cfg(any(mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0g518x))] Id::SYSWWDT1 => Ok(SysrstWwdt1Violation), #[cfg(not(any(mspm0c110x, mspm0c1105_c1106, mspm0g351x, mspm0g151x)))] Id::SYSFLASHECC => Ok(SysrstFlashEccError), Id::SYSCPULOCK => Ok(SysrstCpuLockupViolation), Id::SYSDBG => Ok(SysrstDebugTriggered), Id::SYSSW => Ok(SysrstSwTriggered), Id::CPUDBG => Ok(CpurstDebugTriggered), Id::CPUSW => Ok(CpurstSwTriggered), other => Err(other as u8), } } ================================================ FILE: embassy-mspm0/src/macros.rs ================================================ #![macro_use] #[allow(unused)] macro_rules! new_pin { ($name: ident, $pf_type: expr) => {{ let pin = $name; pin.set_as_pf(pin.pf_num(), $pf_type); Some(pin.into()) }}; } ================================================ FILE: embassy-mspm0/src/mathacl.rs ================================================ //! MATHACL //! //! This HAL implements mathematical calculations performed by the CPU. #![macro_use] use core::f32::consts::PI; use core::marker::PhantomData; use embassy_hal_internal::PeripheralType; use micromath::F32Ext; use crate::Peri; use crate::pac::mathacl::{Mathacl as Regs, vals}; const ERROR_TOLERANCE: f32 = 0.00001; pub enum Precision { High = 31, Medium = 15, Low = 1, } /// Error type for Mathacl operations. #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { ValueInWrongRange, DivideByZero, FaultIQTypeFormat, IQTypeError(IQTypeError), } pub struct Mathacl<'d> { regs: &'static Regs, _phantom: PhantomData<&'d mut ()>, } impl<'d> Mathacl<'d> { /// Mathacl initialization. pub fn new(_instance: Peri<'d, T>) -> Self { // Init power T::regs().gprcm(0).rstctl().write(|w| { w.set_resetstkyclr(vals::Resetstkyclr::CLR); w.set_resetassert(vals::Resetassert::ASSERT); w.set_key(vals::ResetKey::KEY); }); // Enable power T::regs().gprcm(0).pwren().write(|w| { w.set_enable(true); w.set_key(vals::PwrenKey::KEY); }); // init delay, 16 cycles cortex_m::asm::delay(16); Self { regs: T::regs(), _phantom: PhantomData, } } /// Internal helper SINCOS function. fn sincos(&mut self, rad: f32, precision: Precision, sin: bool) -> Result { if rad > PI || rad < -PI { return Err(Error::ValueInWrongRange); } // make division using mathacl let native = self.div_iq(IQType::from_f32(rad, 15, true)?, IQType::from_f32(PI, 15, true)?)?; self.regs.ctl().write(|w| { w.set_func(vals::Func::SINCOS); w.set_numiter(precision as u8); }); // integer part has to be 0 bits self.regs.op1().write(|w| { w.set_data(IQType::from_f32(native, 0, true).unwrap().to_reg()); }); // check if done while self.regs.status().read().busy() == vals::Busy::NOTDONE {} match sin { true => Ok(IQType::from_reg(self.regs.res2().read().data(), 0, true) .unwrap() .to_f32()), false => Ok(IQType::from_reg(self.regs.res1().read().data(), 0, true) .unwrap() .to_f32()), } } /// Calsulates trigonometric sine operation in the range [-1,1) with a give precision. pub fn sin(&mut self, rad: f32, precision: Precision) -> Result { self.sincos(rad, precision, true) } /// Calsulates trigonometric cosine operation in the range [-1,1) with a give precision. pub fn cos(&mut self, rad: f32, precision: Precision) -> Result { self.sincos(rad, precision, false) } pub fn div_i32(&mut self, dividend: i32, divisor: i32) -> Result { if divisor == 0 { return Err(Error::DivideByZero); } else if dividend == 0 { return Ok(0); } let signed = true; self.regs.ctl().write(|w| { w.set_func(vals::Func::DIV); w.set_optype(signed); }); self.regs.op2().write(|w| { w.set_data(divisor as u32); }); self.regs.op1().write(|w| { w.set_data(dividend as u32); }); // check if done while self.regs.status().read().busy() == vals::Busy::NOTDONE {} // read quotient Ok(self.regs.res1().read().data() as i32) } pub fn div_u32(&mut self, dividend: u32, divisor: u32) -> Result { if divisor == 0 { return Err(Error::DivideByZero); } else if dividend == 0 { return Ok(0); } let signed = false; self.regs.ctl().write(|w| { w.set_func(vals::Func::DIV); w.set_optype(signed); }); self.regs.op2().write(|w| { w.set_data(divisor); }); self.regs.op1().write(|w| { w.set_data(dividend); }); // check if done while self.regs.status().read().busy() == vals::Busy::NOTDONE {} // read quotient Ok(self.regs.res1().read().data()) } /// Divide function (DIV) computes with a known dividend and divisor. pub fn div_iq(&mut self, dividend: IQType, divisor: IQType) -> Result { let divisor_value = divisor.to_f32(); if -ERROR_TOLERANCE < divisor_value && divisor_value < ERROR_TOLERANCE { return Err(Error::DivideByZero); } // check if both numbers have the same number of bits if dividend.f_bits != divisor.f_bits { return Err(Error::FaultIQTypeFormat); } // dividen and divisor must have the same signedness if dividend.signed ^ divisor.signed { return Err(Error::FaultIQTypeFormat); } self.regs.ctl().write(|w| { w.set_func(vals::Func::DIV); w.set_optype(dividend.signed); w.set_qval(dividend.f_bits.into()); }); self.regs.op2().write(|w| { w.set_data(divisor.to_reg()); }); self.regs.op1().write(|w| { w.set_data(dividend.to_reg()); }); // check if done while self.regs.status().read().busy() == vals::Busy::NOTDONE {} // read quotient return Ok( IQType::from_reg(self.regs.res1().read().data(), dividend.i_bits.into(), dividend.signed) .unwrap() .to_f32(), ); } } pub(crate) trait SealedInstance { fn regs() -> &'static Regs; } /// Mathacl instance trait #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType {} macro_rules! impl_mathacl_instance { ($instance: ident) => { impl crate::mathacl::SealedInstance for crate::peripherals::$instance { fn regs() -> &'static crate::pac::mathacl::Mathacl { &crate::pac::$instance } } impl crate::mathacl::Instance for crate::peripherals::$instance {} }; } /// Error type for Mathacl operations. #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum IQTypeError { FaultySignParameter, IntPartIsTrimmed, } impl From for Error { fn from(e: IQTypeError) -> Self { Error::IQTypeError(e) } } #[derive(Debug, PartialEq, Copy, Clone)] pub struct IQType { i_bits: u8, f_bits: u8, negative: bool, signed: bool, i_data: u32, f_data: u32, } /// IQType implements 32-bit fixed point numbers with configurable integer and fractional parts. impl IQType { pub fn from_reg(data: u32, i_bits: u8, signed: bool) -> Result { // check if negative let negative = signed && ((1u32 << 31) & data != 0); // total bit count let total_bits = if signed { 31 } else { 32 }; // number of fractional bits let f_bits = total_bits - i_bits; // Compute masks let max_mask = if signed { 0x7FFFFFFF } else { 0xFFFFFFFF }; let (i_mask, f_mask) = if i_bits == 0 { (0, max_mask) } else if i_bits == total_bits { (max_mask, 0) } else { ((1u32 << i_bits) - 1, (1u32 << f_bits) - 1) }; // Compute i_data and f_data let mut i_data = if i_bits == 0 { 0 } else if i_bits == total_bits { data & i_mask } else { (data >> f_bits) & i_mask }; let mut f_data = data & f_mask; // if negative, do 2’s compliment if negative { i_data = !i_data & i_mask; f_data = (!f_data & f_mask) + 1; } Ok(Self { i_bits, f_bits, negative, signed, i_data, f_data, }) } pub fn from_f32(data: f32, i_bits: u8, signed: bool) -> Result { // check if negative let negative = data < 0.0; if !signed && negative { return Err(IQTypeError::FaultySignParameter); } // absolute value let abs = if data < 0.0 { -data } else { data }; // total bit count let total_bits = if signed { 31 } else { 32 }; // number of fractional bits let f_bits: u8 = total_bits - i_bits; let abs_floor = abs.floor(); let i_data = abs_floor as u32; let f_data = ((abs - abs_floor) * (1u32 << f_bits) as f32).round() as u32; // Handle trimming integer part if i_bits == 0 && i_data > 0 { return Err(IQTypeError::IntPartIsTrimmed); } Ok(Self { i_bits, f_bits, negative, signed, i_data, f_data, }) } pub fn to_f32(&self) -> f32 { let mut value = (self.i_data as f32) + (self.f_data as f32) / (1u32 << self.f_bits as u8) as f32; if self.negative { value = -value; } return value; } pub fn to_reg(&self) -> u32 { let mut res: u32 = 0; // total bit count let total_bits: u8 = if self.signed { 31 } else { 32 }; // Compute masks let max_mask = if self.signed { 0x7FFFFFFF } else { 0xFFFFFFFF }; let (i_mask, f_mask) = if self.i_bits == 0 { (0, max_mask) } else if self.i_bits == total_bits { (max_mask, 0) } else { ((1u32 << self.i_bits) - 1, (1u32 << self.f_bits) - 1) }; // calculate result if self.i_bits > 0 { res = self.i_data << self.f_bits & (i_mask << self.f_bits); } if self.f_bits > 0 { res = (self.f_data & f_mask) | res; } // if negative, do 2’s compliment if self.negative { res = !res + 1; } res } } #[cfg(test)] mod tests { use super::*; #[test] fn mathacl_iqtype_errors() { // integer part trimmed let mut test_float = 1.0; assert_eq!( IQType::from_f32(test_float, 0, true), Err(IQTypeError::IntPartIsTrimmed) ); // negative value for unsigned type test_float = -1.0; assert_eq!( IQType::from_f32(test_float, 1, false), Err(IQTypeError::FaultySignParameter) ); } #[test] fn mathacl_iqtype_f32_to_f32() { assert_eq!(IQType::from_f32(0.0, 15, true).unwrap().to_f32(), 0.0); assert_eq!(IQType::from_f32(0.0, 16, false).unwrap().to_f32(), 0.0); assert_eq!(IQType::from_f32(1.5, 16, false).unwrap().to_f32(), 1.5); assert_eq!(IQType::from_f32(1.5, 15, true).unwrap().to_f32(), 1.5); assert_eq!(IQType::from_f32(-1.5, 15, true).unwrap().to_f32(), -1.5); } #[test] fn mathacl_iqtype_reg_to_reg() { assert_eq!(IQType::from_reg(0x0, 15, true).unwrap().to_reg(), 0x0); assert_eq!(IQType::from_reg(0x0, 16, false).unwrap().to_reg(), 0x0); assert_eq!(IQType::from_reg(0x00018000, 15, true).unwrap().to_reg(), 0x00018000); assert_eq!(IQType::from_reg(0x00018000, 16, false).unwrap().to_reg(), 0x00018000); assert_eq!(IQType::from_reg(0xFFFE5556, 15, true).unwrap().to_reg(), 0xFFFE5556); } #[test] fn mathacl_iqtype_f32_to_register() { let mut test_float = 0.0; assert_eq!(IQType::from_f32(test_float, 15, true).unwrap().to_reg(), 0x0); assert_eq!(IQType::from_f32(test_float, 16, false).unwrap().to_reg(), 0x0); test_float = 1.5; assert_eq!(IQType::from_f32(test_float, 15, true).unwrap().to_reg(), 0x00018000); assert_eq!(IQType::from_f32(test_float, 16, false).unwrap().to_reg(), 0x00018000); test_float = -1.5; assert_eq!(IQType::from_f32(test_float, 15, true).unwrap().to_reg(), 0xFFFE8000); test_float = 1.666657; assert_eq!(IQType::from_f32(test_float, 15, true).unwrap().to_reg(), 0x0001AAAA); assert_eq!(IQType::from_f32(test_float, 16, false).unwrap().to_reg(), 0x0001AAAA); test_float = -1.666657; assert_eq!(IQType::from_f32(test_float, 15, true).unwrap().to_reg(), 0xFFFE5556); } #[test] fn mathacl_iqtype_register_to_signed_f32() { let mut test_u32: u32 = 0x7FFFFFFF; let mut result = IQType::from_reg(test_u32, 0, true).unwrap().to_f32(); assert!(result < 1.0 + ERROR_TOLERANCE && result > 1.0 - ERROR_TOLERANCE); test_u32 = 0x0; result = IQType::from_reg(test_u32, 0, true).unwrap().to_f32(); assert!(result < 0.0 + ERROR_TOLERANCE && result > 0.0 - ERROR_TOLERANCE); test_u32 = 0x0001AAAA; result = IQType::from_reg(test_u32, 15, true).unwrap().to_f32(); assert!(result < 1.666657 + ERROR_TOLERANCE && result > 1.666657 - ERROR_TOLERANCE); test_u32 = 0xFFFE5556; result = IQType::from_reg(test_u32, 15, true).unwrap().to_f32(); assert!(result < -1.666657 + ERROR_TOLERANCE && result > -1.666657 - ERROR_TOLERANCE); } } ================================================ FILE: embassy-mspm0/src/time_driver.rs ================================================ use core::cell::{Cell, RefCell}; use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; use core::task::Waker; use critical_section::{CriticalSection, Mutex}; use embassy_time_driver::Driver; use embassy_time_queue_utils::Queue; use mspm0_metapac::interrupt; use mspm0_metapac::tim::Tim; use mspm0_metapac::tim::vals::{Cm, Cvae, CxC, EvtCfg, PwrenKey, Repeat, ResetKey}; use crate::peripherals; use crate::timer::SealedTimer; #[cfg(any(time_driver_timg12, time_driver_timg13))] compile_error!("TIMG12 and TIMG13 are not supported by the time driver yet"); // Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers. #[cfg(time_driver_timb0)] type T = peripherals::TIMB0; #[cfg(time_driver_timg0)] type T = peripherals::TIMG0; #[cfg(time_driver_timg1)] type T = peripherals::TIMG1; #[cfg(time_driver_timg2)] type T = peripherals::TIMG2; #[cfg(time_driver_timg3)] type T = peripherals::TIMG3; #[cfg(time_driver_timg4)] type T = peripherals::TIMG4; #[cfg(time_driver_timg5)] type T = peripherals::TIMG5; #[cfg(time_driver_timg6)] type T = peripherals::TIMG6; #[cfg(time_driver_timg7)] type T = peripherals::TIMG7; #[cfg(time_driver_timg8)] type T = peripherals::TIMG8; #[cfg(time_driver_timg9)] type T = peripherals::TIMG9; #[cfg(time_driver_timg10)] type T = peripherals::TIMG10; #[cfg(time_driver_timg11)] type T = peripherals::TIMG11; #[cfg(time_driver_timg14)] type T = peripherals::TIMG14; #[cfg(time_driver_tima0)] type T = peripherals::TIMA0; #[cfg(time_driver_tima1)] type T = peripherals::TIMA1; // TODO: RTC fn regs() -> Tim { unsafe { Tim::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. fn calc_now(period: u32, counter: u16) -> u64 { ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64) } /// The TIMx driver uses one of the `TIMG` or `TIMA` timer instances to implement a timer with a 32.768 kHz /// tick rate. (TODO: Allow setting the tick rate) /// /// This driver defines a period to be 2^15 ticks. 16-bit timers of course count to 2^16 ticks. /// /// To generate a period every 2^15 ticks, the CC0 value is set to 2^15 and the load value set to 2^16. /// Incrementing the period on a CCU0 and load results in the a period of 2^15 ticks. /// /// For a specific timestamp, load the lower 16 bits into the CC1 value. When the period where the timestamp /// should be enabled is reached, then the CCU1 (CC1 up) interrupt runs to actually wake the timer. /// /// TODO: Compensate for per part variance. This can supposedly be done with the FCC system. /// TODO: Allow using 32-bit timers (TIMG12 and TIMG13). struct TimxDriver { /// Number of 2^15 periods elapsed since boot. period: AtomicU32, /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. alarm: Mutex>, queue: Mutex>, } impl TimxDriver { #[inline(never)] fn init(&'static self, _cs: CriticalSection) { // Clock config // TODO: Configurable tick rate up to 4 MHz (32 kHz for now) let regs = regs(); // Reset timer regs.gprcm(0).rstctl().write(|w| { w.set_resetassert(true); w.set_key(ResetKey::KEY); w.set_resetstkyclr(true); }); // Power up timer regs.gprcm(0).pwren().write(|w| { w.set_enable(true); w.set_key(PwrenKey::KEY); }); // Following the instructions according to SLAU847D 23.2.1: TIMCLK Configuration // 1. Select TIMCLK source regs.clksel().modify(|w| { // Use LFCLK for a 32.768kHz tick rate w.set_lfclk_sel(true); // TODO: Allow MFCLK for configurable tick rate up to 4 MHz // w.set_mfclk_sel(ClkSel::ENABLE); }); // 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate regs.clkdiv().modify(|w| { w.set_ratio(0); // + 1 }); // 3. To be generic across timer instances, we do not use the prescaler. // TODO: mspm0-sdk always sets this, regardless of timer width? regs.commonregs(0).cps().modify(|w| { w.set_pcnt(0); }); regs.pdbgctl().modify(|w| { w.set_free(true); }); // 4. Enable the TIMCLK. regs.commonregs(0).cclkctl().modify(|w| { w.set_clken(true); }); regs.counterregs(0).ctrctl().modify(|w| { // allow counting during debug w.set_repeat(Repeat::REPEAT_3); w.set_cvae(Cvae::ZEROVAL); w.set_cm(Cm::UP); // Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count. // // The reset value of these registers is 0x07, which is a reserved value. // // Looking at a bit representation of the reset value, this appears to be an AND // of 2-input QEI mode and CCCTL_3 ACOND. Given that TIMG14 and TIMA0 have no QEI // and 4 capture and compare channels, this works by accident for those timer units. w.set_czc(CxC::CCTL0); w.set_cac(CxC::CCTL0); w.set_clc(CxC::CCTL0); }); // Middle regs.counterregs(0).cc(0).write_value(0x7FFF as u32); regs.counterregs(0).load().write_value(u16::MAX as u32); // Enable the period interrupts // // This does not appear to ever be set for CPU_INT in the TI SDK and is not technically needed. regs.evt_mode().modify(|w| { w.set_evt_cfg(0, EvtCfg::SOFTWARE); }); regs.cpu_int(0).imask().modify(|w| { w.set_l(true); w.set_ccu0(true); }); unsafe { T::enable_interrupt() }; // Allow the counter to start counting. regs.counterregs(0).ctrctl().modify(|w| { w.set_en(true); }); } #[inline(never)] fn next_period(&self) { let r = regs(); // 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.cpu_int(0).imask().modify(move |w| { let alarm = self.alarm.borrow(cs); let at = alarm.get(); if at < t + 0xC000 { // just enable it. `set_alarm` has already set the correct CC1 val. w.set_ccu1(true); } }) }); } #[inline(never)] fn on_interrupt(&self) { let r = regs(); critical_section::with(|cs| { let mis = r.cpu_int(0).mis().read(); // Advance to next period if overflowed if mis.l() { self.next_period(); r.cpu_int(0).iclr().write(|w| { w.set_l(true); }); } if mis.ccu0() { self.next_period(); r.cpu_int(0).iclr().write(|w| { w.set_ccu0(true); }); } if mis.ccu1() { r.cpu_int(0).iclr().write(|w| { w.set_ccu1(true); }); self.trigger_alarm(cs); } }); } 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()); } } fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { let r = regs(); self.alarm.borrow(cs).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.cpu_int(0).imask().modify(|w| w.set_ccu1(false)); self.alarm.borrow(cs).set(u64::MAX); return false; } // Write the CC1 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. // // Cast to u16 and then u32 to clamp to 16-bit timer limits. r.counterregs(0).cc(1).write_value(timestamp as u16 as u32); // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. let diff = timestamp - t; r.cpu_int(0).imask().modify(|w| w.set_ccu1(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.cpu_int(0).imask().modify(|w| w.set_ccu1(false)); self.alarm.borrow(cs).set(u64::MAX); return false; } // We're confident the alarm will ring in the future. true } } impl Driver for TimxDriver { fn now(&self) -> u64 { let regs = regs(); let period = self.period.load(Ordering::Relaxed); // Ensure the compiler does not read the counter before the period. compiler_fence(Ordering::Acquire); let counter = regs.counterregs(0).ctr().read() as u16; calc_now(period, counter) } 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()); } } }); } } embassy_time_driver::time_driver_impl!(static DRIVER: TimxDriver = TimxDriver { period: AtomicU32::new(0), alarm: Mutex::new(Cell::new(u64::MAX)), queue: Mutex::new(RefCell::new(Queue::new())) }); pub(crate) fn init(cs: CriticalSection) { DRIVER.init(cs); } #[cfg(time_driver_timg0)] #[interrupt] fn TIMG0() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg1)] #[interrupt] fn TIMG1() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg2)] #[interrupt] fn TIMG2() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg3)] #[interrupt] fn TIMG3() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg4)] #[interrupt] fn TIMG4() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg5)] #[interrupt] fn TIMG5() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg6)] #[interrupt] fn TIMG6() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg7)] #[interrupt] fn TIMG7() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg8)] #[interrupt] fn TIMG8() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg9)] #[interrupt] fn TIMG9() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg10)] #[interrupt] fn TIMG10() { DRIVER.on_interrupt(); } #[cfg(time_driver_timg11)] #[interrupt] fn TIMG11() { DRIVER.on_interrupt(); } // TODO: TIMG12 and TIMG13 #[cfg(time_driver_timg14)] #[interrupt] fn TIMG14() { DRIVER.on_interrupt(); } #[cfg(time_driver_tima0)] #[interrupt] fn TIMA0() { DRIVER.on_interrupt(); } #[cfg(time_driver_tima1)] #[interrupt] fn TIMA1() { DRIVER.on_interrupt(); } ================================================ FILE: embassy-mspm0/src/timer.rs ================================================ #![macro_use] /// Amount of bits of a timer. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum TimerBits { /// 16 bits. Bits16, /// 32 bits. Bits32, } #[allow(private_bounds)] pub trait Timer: SealedTimer + 'static { /// Amount of bits this timer has. const BITS: TimerBits; } pub(crate) trait SealedTimer { /// 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 (); /// Enable the interrupt corresponding to this timer. unsafe fn enable_interrupt(); } macro_rules! impl_timer { ($name: ident, $bits: ident) => { impl crate::timer::SealedTimer for crate::peripherals::$name { fn regs() -> *mut () { crate::pac::$name.as_ptr() } unsafe fn enable_interrupt() { use embassy_hal_internal::interrupt::InterruptExt; crate::interrupt::$name.unpend(); crate::interrupt::$name.enable(); } } impl crate::timer::Timer for crate::peripherals::$name { const BITS: crate::timer::TimerBits = crate::timer::TimerBits::$bits; } }; } ================================================ FILE: embassy-mspm0/src/trng.rs ================================================ //! True Random Number Generator (TRNG) driver. use core::fmt::Display; use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; use cortex_m::asm; use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; use mspm0_metapac::trng::regs::Int; use mspm0_metapac::trng::vals::Cmd::*; use mspm0_metapac::trng::vals::{self, PwrenKey, Ratio, RstctlKey}; use rand_core::{TryCryptoRng, TryRngCore}; use crate::peripherals::TRNG; use crate::sealed; static WAKER: AtomicWaker = AtomicWaker::new(); /// Decimation rate marker types. See [`DecimRate`]. pub trait SecurityMarker: sealed::Sealed { type DecimRate: DecimRate; } /// Marker type for cryptographic decimation rate. See [`CryptoDecimRate`]. pub struct Crypto; impl sealed::Sealed for Crypto {} impl SecurityMarker for Crypto { type DecimRate = CryptoDecimRate; } /// Marker type for fast decimation rate. See [`FastDecimRate`]. pub struct Fast; impl sealed::Sealed for Fast {} impl SecurityMarker for Fast { type DecimRate = FastDecimRate; } /// The decimation rate settings for the TRNG. /// Higher decimation rates improve the quality of the random numbers at the cost of speed. /// /// L-series TRM 13.2.2: It is required to use a decimation rate of at least 4 for cryptographic applications. /// /// See [`FastDecimRate`] and [`CryptoDecimRate`] for available options. pub trait DecimRate: sealed::Sealed + Into + Copy {} /// Fast decimation rates for non-cryptographic applications. /// See [`DecimRate`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum FastDecimRate { Decim1, Decim2, Decim3, } impl sealed::Sealed for FastDecimRate {} impl Into for FastDecimRate { fn into(self) -> vals::DecimRate { match self { Self::Decim1 => vals::DecimRate::DECIM_1, Self::Decim2 => vals::DecimRate::DECIM_2, Self::Decim3 => vals::DecimRate::DECIM_3, } } } impl DecimRate for FastDecimRate {} /// Cryptographic decimation rates for cryptographic applications. /// See [`DecimRate`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CryptoDecimRate { Decim4, Decim5, Decim6, Decim7, Decim8, } impl sealed::Sealed for CryptoDecimRate {} impl Into for CryptoDecimRate { fn into(self) -> vals::DecimRate { match self { Self::Decim4 => vals::DecimRate::DECIM_4, Self::Decim5 => vals::DecimRate::DECIM_5, Self::Decim6 => vals::DecimRate::DECIM_6, Self::Decim7 => vals::DecimRate::DECIM_7, Self::Decim8 => vals::DecimRate::DECIM_8, } } } impl DecimRate for CryptoDecimRate {} /// Represents errors that can arise during initialization with [`Trng::new`] or reading a value from the [`Trng`]. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// L-series TRM /// The digital startup health test is run by application software when powering up the TRNG module. /// This built-in self-test verifies that the digital block is functioning properly by running predefined sequences of digital samples through the complete digital block while checking for expected outputs. /// The test sequence includes eight tests (indexed 0-7.) /// /// This failure indicates an irrecoverable fault in the digital block detected during startup. DigitalSelfTestFailed(u8), /// L-series TRM 13.2.4.2: /// The analog startup health test is run by application software when powering up the TRNG module. /// This test verifies that the analog block is functioning properly by capturing 4,096 consecutive analog samples and verifying that the samples pass a health test. /// /// The driver only raises this error after three subsequent failures during initialization. AnalogSelfTestFailed, /// L-series TRM 13.2.4.3.1: /// The repetition count test quickly detects failures that cause the entropy source to remain stuck on a single output value for an extended period of time. /// The repetition count test fails if the entropy source outputs the same bit value for 135 consecutive samples. /// ///
This error may occur by chance and can be retried up to two additional times after calling Trng::fail_reset.
RepetitionCountTestFailed, /// L-series TRM 13.2.4.3.2: /// The adaptive proportion test detects failures that cause a disproportionate number of samples to be the same bit value and/or bit pattern over a window of 1024 samples. /// The adaptive proportion test fails if any of the conditions in Table 13-1 are violated. /// ///
This error may occur by chance and can be retried up to two additional times after calling Trng::fail_reset.
AdaptiveProportionTestFailed, } impl Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::DigitalSelfTestFailed(n) => write!(f, "Digital startup self-test (index: {n}) failed"), Self::AnalogSelfTestFailed => write!(f, "Analog startup self-test failed"), Self::RepetitionCountTestFailed => write!(f, "Repetition count runtime self-test failed"), Self::AdaptiveProportionTestFailed => write!(f, "Adaptive proportion runtime self-test failed"), } } } impl core::error::Error for Error {} /// True Random Number Generator (TRNG) Driver for MSPM0 series. /// /// The driver provides blocking random numbers with [`TryRngCore`] methods and asynchronous counterparts in [`Trng::async_read_u32`], [`Trng::async_read_u64`], and [`Trng::async_read_bytes`]. /// /// The TRNG can be configured with different decimation rates. See [`DecimRate`], [`FastDecimRate`], and [`CryptoDecimRate`]. /// The TRNG can be instantiated with [`Trng::new`], [`Trng::new_fast`], or [`Trng::new_secure`]. /// /// Usage example: /// ```no_run /// #![no_std] /// #![no_main] /// /// use embassy_executor::Spawner; /// use embassy_mspm0::Config; /// use embassy_mspm0::trng::Trng; /// use rand_core::TryRngCore; /// use {defmt_rtt as _, panic_halt as _}; /// /// #[embassy_executor::main] /// async fn main(_spawner: Spawner) -> ! { /// let p = embassy_mspm0::init(Config::default()); /// let mut trng = Trng::new(p.TRNG).expect("Failed to initialize TRNG"); /// let mut randomness = [0u8; 16]; /// /// loop { /// trng.fill_bytes(&mut randomness).unwrap(); /// assert_ne!(randomness, [0u8; 16]); /// } /// } /// ``` pub struct Trng<'d, L: SecurityMarker> { inner: TrngInner<'d>, _security_level: PhantomData, } impl<'d> Trng<'d, Crypto> { /// Setup a TRNG driver with the default safe decimation rate (Decim4). #[inline(always)] pub fn new(peripheral: Peri<'d, TRNG>) -> Result { Self::new_secure(peripheral, CryptoDecimRate::Decim4) } } // Split off new methods to allow type inference when providing the decimation rate. impl<'d> Trng<'d, Fast> { /// Setup a TRNG driver with a specific fast decimation rate. /// ///
The created TRNG is not suitable for cryptography. Use Trng::new_secure instead.
#[inline(always)] pub fn new_fast(peripheral: Peri<'d, TRNG>, rate: FastDecimRate) -> Result { Self::new_with_rate(peripheral, rate) } } impl<'d> Trng<'d, Crypto> { /// Setup a TRNG driver with a specific cryptographic decimation rate. #[inline(always)] pub fn new_secure(peripheral: Peri<'d, TRNG>, rate: CryptoDecimRate) -> Result { Self::new_with_rate(peripheral, rate) } } impl<'d, D: SecurityMarker> Trng<'d, D> { #[inline(always)] fn new_with_rate(_peripheral: Peri<'d, TRNG>, rate: D::DecimRate) -> Result { Ok(Self { inner: TrngInner::new(rate.into())?, _security_level: PhantomData, }) } /// L-series TRM 13.2.5.2(10): procedure for recovering from a failed read. /// /// This method reinitializes the TRNG which may take some time to complete. #[inline(always)] pub fn fail_reset(&mut self) -> Result<(), Error> { self.inner.fail_reset() } /// Asynchronously read a 32-bit random value from the TRNG. /// /// The synchronous counterpart is given by [`TryRngCore::try_next_u32`]. /// /// As with the [`synchronous`](TryRngCore) methods, an [`Err`] may be retried up to two times after calling [`Trng::fail_reset`]. #[cfg(feature = "rt")] #[inline(always)] pub async fn async_read_u32(&mut self) -> Result { self.inner.async_read_u32().await } /// Asynchronously read a 64-bit random value from the TRNG. /// /// The synchronous counterpart is given by [`TryRngCore::try_next_u64`]. /// /// As with the [`synchronous`](TryRngCore) methods, an [`Err`] may be retried up to two times after calling [`Trng::fail_reset`]. #[cfg(feature = "rt")] #[inline(always)] pub async fn async_read_u64(&mut self) -> Result { self.inner.async_read_u64().await } /// Asynchronously fill `dest` with random bytes from the TRNG. /// /// The synchronous counterpart is given by [`TryRngCore::try_fill_bytes`]. /// /// As with the [`synchronous`](TryRngCore) methods, an [`Err`] may be retried up to two times after calling [`Trng::fail_reset`]. /// /// > **Note** /// When an error condition occurs, the buffer may be partially filled. #[cfg(feature = "rt")] #[inline(always)] pub async fn async_read_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { self.inner.async_read_bytes(dest).await } } /// Implements the fallible [`TryRngCore`]. /// /// If any of the methods give an [`Err`], the operation may be retried up to two times after calling [`Trng::fail_reset`]. impl TryRngCore for Trng<'_, D> { type Error = Error; #[inline(always)] fn try_next_u32(&mut self) -> Result { self.inner.try_next_u32() } #[inline(always)] fn try_next_u64(&mut self) -> Result { self.inner.try_next_u64() } /// > **Note** /// When an error condition occurs, the buffer may be partially filled. #[inline(always)] fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { self.inner.try_fill_bytes(dest) } } impl TryCryptoRng for Trng<'_, Crypto> {} // TODO: Replace this when the MCLK rate can be adjusted. #[cfg(any(mspm0c110x, mspm0c1105_c1106))] fn get_mclk_frequency() -> u32 { 24_000_000 } // TODO: Replace this when the MCLK rate can be adjusted. #[cfg(any( mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0h321x, mspm0l110x, mspm0l122x, mspm0l130x, mspm0l134x, mspm0l222x ))] fn get_mclk_frequency() -> u32 { 32_000_000 } // Inner TRNG driver implementation. Used to reduce monomorphization bloat. struct TrngInner<'d> { decim_rate: vals::DecimRate, _phantom: PhantomData<&'d ()>, } impl TrngInner<'_> { fn new(decim_rate: vals::DecimRate) -> Result { let mut trng = TrngInner { decim_rate: decim_rate, _phantom: PhantomData, }; trng.reset(); // L-series TRM 13.2.5.2 trng.power_on(); // 1. Power on the TRNG peripheral. asm::delay(16); // Delay 16 cycles for power-on. trng.init()?; // 2-8. Initialize the TRNG. Ok(trng) } fn fail_reset(&mut self) -> Result<(), Error> { regs().iclr().write(|w| w.set_irq_health_fail(true)); self.set_cmd(PWR_OFF); self.init() } fn init(&mut self) -> Result<(), Error> { // L-series TRM 13.2.5.2 self.set_div(); // 2. Set the clock divider. regs().imask().write_value(Int::default()); // 3. Disable all interrupts. self.set_cmd(NORM_FUNC); // 4. Set to normal function mode. self.dig_test()?; // 5. Perform digital block start-up self-tests. self.ana_test()?; // 6. Perform analog block start-up self-test. self.clr_rdy(); // 7.a Clear IRQ_CAPTURED_RDY_IRQ. self.set_decim_rate(); // 7.b Set decimation rate. self.set_cmd(NORM_FUNC); // 7.b Set to normal function mode again after changing decimation rate. _ = self.read(); // 8. By 13.2.4.1, must discard first value. Ok(()) } fn reset(&mut self) { regs().gprcm().rstctl().write(|w| { w.set_key(RstctlKey::KEY); w.set_resetassert(true); }); } fn power_on(&mut self) { regs().gprcm().pwren().write(|w| { w.set_key(PwrenKey::KEY); w.set_enable(true); }); } fn power_off(&mut self) { regs().gprcm().pwren().write(|w| w.set_key(PwrenKey::KEY)); } fn set_div(&mut self) { // L-series TRM 13.2.2: The TRNG is derived from MCLK. Datasheets specify 9.5-20 MHz range. let freq = get_mclk_frequency(); let ratio = if freq > 160_000_000 { panic!("MCLK frequency {} > 160 MHz is not compatible with the TRNG", freq) } else if freq >= 80_000_000 { Ratio::DIV_BY_8 } else if freq >= 60_000_000 { Ratio::DIV_BY_6 } else if freq >= 40_000_000 { Ratio::DIV_BY_4 } else if freq >= 20_000_000 { Ratio::DIV_BY_2 } else if freq >= 9_500_000 { Ratio::DIV_BY_1 } else { panic!("MCLK frequency {} < 9.5 MHz is not compatible with the TRNG", freq) }; regs().clkdiv().write(|w| w.set_ratio(ratio)); } fn set_decim_rate(&mut self) { regs().ctl().modify(|w| w.set_decim_rate(self.decim_rate)); } fn clr_rdy(&mut self) { regs().iclr().write(|w| w.set_irq_captured_rdy(true)); } fn set_cmd(&mut self, cmd: vals::Cmd) { regs().iclr().write(|w| w.set_irq_cmd_done(true)); regs().ctl().modify(|w| w.set_cmd(cmd)); while !regs().ris().read().irq_cmd_done() {} } fn dig_test(&mut self) -> Result<(), Error> { self.set_cmd(PWRUP_DIG); let results = regs().test_results().read(); for n in 0..8u8 { // 13.2.4.1: Digital tests must pass. if !results.dig_test(n as usize) { return Err(Error::DigitalSelfTestFailed(n)); } } Ok(()) } fn ana_test(&mut self) -> Result<(), Error> { // 13.2.4.2: Analog tests have a small chance to fail, so try up to 3 times. for _ in 0..3 { self.set_cmd(PWRUP_ANA); let results = regs().test_results().read(); if !results.ana_test() { self.set_cmd(PWR_OFF); } else { return Ok(()); } } Err(Error::AnalogSelfTestFailed) } fn poll(&mut self) -> Poll> { let ris = regs().ris().read(); if ris.irq_health_fail() { // 10. The TRNG failed the health check. let stat = regs().stat().read(); if stat.adap_fail() { Poll::Ready(Err(Error::AdaptiveProportionTestFailed)) } else if stat.rep_fail() { Poll::Ready(Err(Error::RepetitionCountTestFailed)) } else { unreachable!("Unexpected TRNG health failure type") } } else if ris.irq_captured_rdy() { // 9. When data is ready, read it. self.clr_rdy(); let data = regs().data_capture().read(); Poll::Ready(Ok(data)) } else { Poll::Pending } } fn read(&mut self) -> Result { loop { if let Poll::Ready(r) = self.poll() { return r; } } } #[cfg(feature = "rt")] async fn async_read_u32(&mut self) -> Result { poll_fn(|cx| { WAKER.register(cx.waker()); let result = self.poll(); if result.is_pending() { // Enable interrupts regs().imask().write(|w| { w.set_irq_captured_rdy(true); w.set_irq_health_fail(true); }); } result }) .await } #[cfg(feature = "rt")] async fn async_read_u64(&mut self) -> Result { let v1 = u64::from(self.async_read_u32().await?); let v2 = u64::from(self.async_read_u32().await?); Ok(v2 << 32 | v1) } #[cfg(feature = "rt")] async fn async_read_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { let mut left = dest; while left.len() >= 4 { let (l, r) = left.split_at_mut(4); left = r; let chunk = self.async_read_u32().await?.to_ne_bytes(); l.copy_from_slice(&chunk); } if !left.is_empty() { let chunk = self.async_read_u32().await?.to_ne_bytes(); left.copy_from_slice(&chunk[..left.len()]); } Ok(()) } } impl Drop for TrngInner<'_> { fn drop(&mut self) { regs().imask().write_value(Int::default()); // Disable all interrupts. self.power_off(); } } impl TryRngCore for TrngInner<'_> { type Error = Error; fn try_next_u32(&mut self) -> Result { self.read() } fn try_next_u64(&mut self) -> Result { let v1 = u64::from(self.try_next_u32()?); let v2 = u64::from(self.try_next_u32()?); Ok(v2 << 32 | v1) } /// > **Note** /// When an error condition occurs, the buffer may be partially filled. fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { let mut left = dest; while left.len() >= 4 { let (l, r) = left.split_at_mut(4); left = r; let chunk = self.try_next_u32()?.to_ne_bytes(); l.copy_from_slice(&chunk); } if !left.is_empty() { let chunk = self.try_next_u32()?.to_ne_bytes(); left.copy_from_slice(&chunk[..left.len()]); } Ok(()) } } fn regs() -> crate::pac::trng::Trng { crate::pac::TRNG } // This symbol is weakly defined as DefaultHandler and is called by the interrupt group implementation. // Defining this as no_mangle is required so that the linker will pick this up. #[cfg(feature = "rt")] #[unsafe(no_mangle)] #[allow(non_snake_case)] fn TRNG() { regs().imask().write_value(Int::default()); // Disable all interrupts. WAKER.wake(); } ================================================ FILE: embassy-mspm0/src/uart/buffered.rs ================================================ use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::slice; use core::sync::atomic::{AtomicU8, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; use embassy_hal_internal::interrupt::InterruptExt; use embassy_sync::waitqueue::AtomicWaker; use embedded_hal_nb::nb; use crate::gpio::{AnyPin, SealedPin}; use crate::interrupt::typelevel::Binding; use crate::pac::uart::Uart as Regs; use crate::uart::{Config, ConfigError, CtsPin, Error, Info, Instance, RtsPin, RxPin, State, TxPin}; use crate::{Peri, interrupt}; /// Interrupt handler. pub struct BufferedInterruptHandler { _uart: PhantomData, } impl interrupt::typelevel::Handler for BufferedInterruptHandler { unsafe fn on_interrupt() { on_interrupt(T::info().regs, T::buffered_state()) } } /// Bidirectional buffered UART which acts as a combination of [`BufferedUartTx`] and [`BufferedUartRx`]. pub struct BufferedUart<'d> { rx: BufferedUartRx<'d>, tx: BufferedUartTx<'d>, } impl SetConfig for BufferedUart<'_> { 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. pub fn new( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner( uart, new_pin!(rx, config.rx_pf()), new_pin!(tx, config.tx_pf()), None, None, tx_buffer, rx_buffer, config, ) } /// Create a new bidirectional buffered UART with request-to-send and clear-to-send pins pub fn new_with_rtscts( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, rts: Peri<'d, impl RtsPin>, cts: Peri<'d, impl CtsPin>, _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner( uart, new_pin!(rx, config.rx_pf()), new_pin!(tx, config.tx_pf()), new_pin!(rts, config.rts_pf()), new_pin!(cts, config.cts_pf()), tx_buffer, rx_buffer, config, ) } /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { self.tx.set_config(config)?; self.rx.set_config(config) } /// Set baudrate pub fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { self.rx.set_baudrate(baudrate) } /// 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 buffer, blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { self.tx.blocking_flush() } /// Check if UART is busy. pub fn busy(&self) -> bool { self.tx.busy() } /// Read from UART RX buffer, blocking execution until done. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { self.rx.blocking_read(buffer) } /// Send break character. pub fn send_break(&mut self) { self.tx.send_break() } /// Split into separate RX and TX handles. pub fn split(self) -> (BufferedUartTx<'d>, BufferedUartRx<'d>) { (self.tx, self.rx) } /// Split into separate RX and TX handles. pub fn split_ref(&mut self) -> (BufferedUartTx<'_>, BufferedUartRx<'_>) { ( BufferedUartTx { info: self.tx.info, state: self.tx.state, tx: self.tx.tx.as_mut().map(Peri::reborrow), cts: self.tx.cts.as_mut().map(Peri::reborrow), reborrowed: true, }, BufferedUartRx { info: self.rx.info, state: self.rx.state, rx: self.rx.rx.as_mut().map(Peri::reborrow), rts: self.rx.rts.as_mut().map(Peri::reborrow), reborrowed: true, }, ) } } /// Rx-only buffered UART. /// /// Can be obtained from [`BufferedUart::split`], or can be constructed independently, /// if you do not need the transmitting half of the driver. pub struct BufferedUartRx<'d> { info: &'static Info, state: &'static BufferedState, rx: Option>, rts: Option>, reborrowed: bool, } impl SetConfig for BufferedUartRx<'_> { type Config = Config; type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_config(config) } } impl<'d> BufferedUartRx<'d> { /// Create a new rx-only buffered UART with no hardware flow control. /// /// Useful if you only want Uart Rx. It saves 1 pin. pub fn new( uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, _irq: impl Binding>, rx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner(uart, new_pin!(rx, config.rx_pf()), None, rx_buffer, config) } /// Create a new rx-only buffered UART with a request-to-send pin pub fn new_with_rts( uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, rts: Peri<'d, impl RtsPin>, _irq: impl Binding>, rx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner( uart, new_pin!(rx, config.rx_pf()), new_pin!(rts, config.rts_pf()), rx_buffer, config, ) } /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { if let Some(ref rx) = self.rx { rx.update_pf(config.rx_pf()); } if let Some(ref rts) = self.rts { rts.update_pf(config.rts_pf()); } super::reconfigure(&self.info, &self.state.state, config) } /// Set baudrate pub fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { super::set_baudrate(&self.info, self.state.state.clock.load(Ordering::Relaxed), baudrate) } /// Read from UART RX buffer, blocking execution until done. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { self.blocking_read_inner(buffer) } } impl Drop for BufferedUartRx<'_> { fn drop(&mut self) { if !self.reborrowed { let state = self.state; // SAFETY: RX is being dropped (and is not reborrowed), so the ring buffer must be deinitialized // in order to meet the requirements of init. unsafe { state.rx_buf.deinit(); } // TX is inactive if the buffer is not available. If this is true, then disable the // interrupt handler since we are running in RX only mode. 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()); } } } /// Tx-only buffered UART. /// /// Can be obtained from [`BufferedUart::split`], or can be constructed independently, /// if you do not need the receiving half of the driver. pub struct BufferedUartTx<'d> { info: &'static Info, state: &'static BufferedState, tx: Option>, cts: Option>, reborrowed: bool, } impl SetConfig for BufferedUartTx<'_> { type Config = Config; type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { self.set_config(config) } } impl<'d> BufferedUartTx<'d> { /// Create a new tx-only buffered UART with no hardware flow control. /// /// Useful if you only want Uart Tx. It saves 1 pin. pub fn new( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, _irq: impl Binding>, tx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner(uart, new_pin!(tx, config.tx_pf()), None, tx_buffer, config) } /// Create a new tx-only buffered UART with a clear-to-send pin pub fn new_with_rts( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, cts: Peri<'d, impl CtsPin>, _irq: impl Binding>, tx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner( uart, new_pin!(tx, config.tx_pf()), new_pin!(cts, config.cts_pf()), tx_buffer, config, ) } /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { if let Some(ref tx) = self.tx { tx.update_pf(config.tx_pf()); } if let Some(ref cts) = self.cts { cts.update_pf(config.cts_pf()); } super::reconfigure(self.info, &self.state.state, config) } /// Set baudrate pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { super::set_baudrate(&self.info, self.state.state.clock.load(Ordering::Relaxed), baudrate) } /// Write to UART TX buffer, blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { self.blocking_write_inner(buffer) } /// Flush UART TX buffer, blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { let state = self.state; loop { if state.tx_buf.is_empty() { return Ok(()); } } } /// Check if UART is busy. pub fn busy(&self) -> bool { super::busy(self.info.regs) } /// Send break character pub fn send_break(&mut self) { let r = self.info.regs; r.lcrh().modify(|w| { w.set_brk(true); }); } } impl Drop for BufferedUartTx<'_> { fn drop(&mut self) { if !self.reborrowed { let state = self.state; // SAFETY: TX is being dropped (and is not reborrowed), so the ring buffer must be deinitialized // in order to meet the requirements of init. unsafe { state.tx_buf.deinit(); } // RX is inactive if the buffer is not available. If this is true, then disable the // interrupt handler since we are running in TX only mode. 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()); } } } impl embedded_io_async::ErrorType for BufferedUart<'_> { type Error = Error; } impl embedded_io_async::ErrorType for BufferedUartRx<'_> { type Error = Error; } impl embedded_io_async::ErrorType for BufferedUartTx<'_> { type Error = Error; } impl embedded_io_async::Read for BufferedUart<'_> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.read(buf).await } } impl embedded_io_async::Read for BufferedUartRx<'_> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read_inner(buf).await } } impl embedded_io_async::ReadReady for BufferedUart<'_> { fn read_ready(&mut self) -> Result { self.rx.read_ready() } } impl embedded_io_async::ReadReady for BufferedUartRx<'_> { fn read_ready(&mut self) -> Result { self.read_ready_inner() } } impl embedded_io_async::BufRead for BufferedUart<'_> { 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 embedded_io_async::BufRead for BufferedUartRx<'_> { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { self.fill_buf_inner().await } fn consume(&mut self, amt: usize) { self.consume_inner(amt); } } impl embedded_io_async::Write for BufferedUart<'_> { async fn write(&mut self, buf: &[u8]) -> Result { self.tx.write_inner(buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { self.tx.flush_inner().await } } impl embedded_io_async::Write for BufferedUartTx<'_> { async fn write(&mut self, buf: &[u8]) -> Result { self.write_inner(buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { self.flush_inner().await } } impl embedded_io::Read for BufferedUart<'_> { fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.read(buf) } } impl embedded_io::Read for BufferedUartRx<'_> { fn read(&mut self, buf: &mut [u8]) -> Result { self.blocking_read_inner(buf) } } impl embedded_io::Write for BufferedUart<'_> { fn write(&mut self, buf: &[u8]) -> Result { self.tx.write(buf) } fn flush(&mut self) -> Result<(), Self::Error> { self.tx.flush() } } impl embedded_io::Write for BufferedUartTx<'_> { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write_inner(buf) } fn flush(&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 { Error::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, Error::Noise => embedded_hal_nb::serial::ErrorKind::Noise, Error::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, Error::Parity => embedded_hal_nb::serial::ErrorKind::Parity, Error::Break => embedded_hal_nb::serial::ErrorKind::Other, } } } impl embedded_hal_nb::serial::ErrorType for BufferedUart<'_> { type Error = Error; } impl embedded_hal_nb::serial::ErrorType for BufferedUartRx<'_> { type Error = Error; } impl embedded_hal_nb::serial::ErrorType for BufferedUartTx<'_> { type Error = Error; } impl embedded_hal_nb::serial::Read for BufferedUart<'_> { fn read(&mut self) -> nb::Result { self.rx.read() } } impl embedded_hal_nb::serial::Read for BufferedUartRx<'_> { fn read(&mut self) -> nb::Result { if self.info.regs.stat().read().rxfe() { return Err(nb::Error::WouldBlock); } super::read_with_error(self.info.regs).map_err(nb::Error::Other) } } impl embedded_hal_nb::serial::Write for BufferedUart<'_> { fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { self.tx.write(word) } fn flush(&mut self) -> nb::Result<(), Self::Error> { self.tx.flush() } } impl embedded_hal_nb::serial::Write for BufferedUartTx<'_> { fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[word]).map(drop).map_err(nb::Error::Other) } fn flush(&mut self) -> nb::Result<(), Self::Error> { self.blocking_flush().map_err(nb::Error::Other) } } // Impl details /// Buffered UART state. pub(crate) struct BufferedState { /// non-buffered UART state. This is inline in order to avoid [`BufferedUartRx`]/Tx /// needing to carry around a 2nd static reference and waste another 4 bytes. state: State, rx_waker: AtomicWaker, rx_buf: RingBuffer, tx_waker: AtomicWaker, tx_buf: RingBuffer, rx_error: AtomicU8, } // these must match bits 8..12 in RXDATA, but shifted by 8 to the right const RXE_NOISE: u8 = 16; const RXE_OVERRUN: u8 = 8; const RXE_BREAK: u8 = 4; const RXE_PARITY: u8 = 2; const RXE_FRAMING: u8 = 1; impl BufferedState { pub const fn new() -> Self { Self { state: State::new(), rx_waker: AtomicWaker::new(), rx_buf: RingBuffer::new(), tx_waker: AtomicWaker::new(), tx_buf: RingBuffer::new(), rx_error: AtomicU8::new(0), } } } impl<'d> BufferedUart<'d> { fn new_inner( _peri: Peri<'d, T>, rx: Option>, tx: Option>, rts: Option>, cts: Option>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Result { let info = T::info(); let state = T::buffered_state(); let mut this = Self { tx: BufferedUartTx { info, state, tx, cts, reborrowed: false, }, rx: BufferedUartRx { info, state, rx, rts, reborrowed: 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; assert!(!tx_buffer.is_empty()); assert!(!rx_buffer.is_empty()); init_buffers(info, state, Some(tx_buffer), Some(rx_buffer)); super::enable(info.regs); super::configure( info, &state.state, config, true, self.rx.rts.is_some(), true, self.tx.cts.is_some(), )?; info.regs.cpu_int(0).imask().modify(|w| { w.set_rxint(true); }); info.interrupt.unpend(); unsafe { info.interrupt.enable() }; Ok(()) } } impl<'d> BufferedUartRx<'d> { fn new_inner( _peri: Peri<'d, T>, rx: Option>, rts: Option>, rx_buffer: &'d mut [u8], config: Config, ) -> Result { let mut this = Self { info: T::info(), state: T::buffered_state(), rx, rts, reborrowed: false, }; this.enable_and_configure(rx_buffer, &config)?; Ok(this) } fn enable_and_configure(&mut self, rx_buffer: &'d mut [u8], config: &Config) -> Result<(), ConfigError> { let info = self.info; let state = self.state; init_buffers(info, state, None, Some(rx_buffer)); super::enable(info.regs); super::configure(info, &self.state.state, config, true, self.rts.is_some(), false, false)?; info.regs.cpu_int(0).imask().modify(|w| { w.set_rxint(true); }); info.interrupt.unpend(); unsafe { info.interrupt.enable() }; Ok(()) } async fn read_inner(&self, buf: &mut [u8]) -> Result { poll_fn(move |cx| { let state = self.state; if let Poll::Ready(r) = self.try_read(buf) { return Poll::Ready(r); } state.rx_waker.register(cx.waker()); Poll::Pending }) .await } fn blocking_read_inner(&self, buffer: &mut [u8]) -> Result { loop { match self.try_read(buffer) { Poll::Ready(res) => return res, Poll::Pending => continue, } } } fn fill_buf_inner(&self) -> impl Future> { poll_fn(move |cx| { let mut rx_reader = unsafe { self.state.rx_buf.reader() }; let (p, n) = rx_reader.pop_buf(); let result = if n == 0 { match Self::get_rx_error(self.state) { None => { self.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_inner(&self, amt: usize) { let mut rx_reader = unsafe { self.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. self.info.regs.cpu_int(0).imask().modify(|w| { w.set_rxint(true); w.set_rtout(true); }); } /// we are ready to read if there is data in the buffer fn read_ready_inner(&self) -> Result { Ok(!self.state.rx_buf.is_empty()) } fn try_read(&self, buf: &mut [u8]) -> Poll> { let state = self.state; if buf.is_empty() { return Poll::Ready(Ok(0)); } 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(state) { 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. self.info.regs.cpu_int(0).imask().modify(|w| { w.set_rxint(true); w.set_rtout(true); }); Poll::Ready(result) } fn get_rx_error(state: &BufferedState) -> Option { // Cortex-M0 has does not support atomic swap, so we must do two operations. let errs = critical_section::with(|_cs| { let errs = state.rx_error.load(Ordering::Relaxed); state.rx_error.store(0, Ordering::Relaxed); errs }); if errs & RXE_NOISE != 0 { Some(Error::Noise) } else 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 } } } impl<'d> BufferedUartTx<'d> { fn new_inner( _peri: Peri<'d, T>, tx: Option>, cts: Option>, tx_buffer: &'d mut [u8], config: Config, ) -> Result { let mut this = Self { info: T::info(), state: T::buffered_state(), tx, cts, reborrowed: false, }; this.enable_and_configure(tx_buffer, &config)?; Ok(this) } async fn write_inner(&self, buf: &[u8]) -> Result { poll_fn(move |cx| { let state = self.state; if buf.is_empty() { return Poll::Ready(Ok(0)); } 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. self.info.interrupt.pend(); Poll::Ready(Ok(n)) }) .await } fn blocking_write_inner(&self, buffer: &[u8]) -> Result { let state = self.state; loop { let empty = state.tx_buf.is_empty(); // SAFETY: tx buf must be initialized if BufferedUartTx exists. let mut tx_writer = unsafe { state.tx_buf.writer() }; let data = tx_writer.push_slice(); if !data.is_empty() { let n = data.len().min(buffer.len()); data[..n].copy_from_slice(&buffer[..n]); tx_writer.push_done(n); if empty { self.info.interrupt.pend(); } return Ok(n); } } } async fn flush_inner(&self) -> Result<(), Error> { poll_fn(move |cx| { let state = self.state; if !state.tx_buf.is_empty() { state.tx_waker.register(cx.waker()); return Poll::Pending; } Poll::Ready(Ok(())) }) .await } fn enable_and_configure(&mut self, tx_buffer: &'d mut [u8], config: &Config) -> Result<(), ConfigError> { let info = self.info; let state = self.state; init_buffers(info, state, Some(tx_buffer), None); super::enable(info.regs); super::configure(info, &state.state, config, false, false, true, self.cts.is_some())?; info.regs.cpu_int(0).imask().modify(|w| { w.set_rxint(true); }); info.interrupt.unpend(); unsafe { info.interrupt.enable() }; Ok(()) } } fn init_buffers<'d>( info: &Info, state: &BufferedState, tx_buffer: Option<&'d mut [u8]>, rx_buffer: Option<&'d mut [u8]>, ) { 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) }; } info.regs.cpu_int(0).imask().modify(|w| { w.set_nerr(true); w.set_frmerr(true); w.set_parerr(true); w.set_brkerr(true); w.set_ovrerr(true); }); } fn on_interrupt(r: Regs, state: &'static BufferedState) { let int = r.cpu_int(0).mis().read(); // Per https://github.com/embassy-rs/embassy/pull/1458, both buffered and unbuffered handlers may be bound. if super::dma_enabled(r) { return; } // RX if state.rx_buf.is_available() { // SAFETY: RX must have been initialized if RXE is set. let mut rx_writer = unsafe { state.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 { let stat = r.stat().read(); if stat.rxfe() { break; } let data = r.rxdata().read(); if (data.0 >> 8) != 0 { // Cortex-M0 does not support atomic fetch_or, must do 2 operations. critical_section::with(|_cs| { let mut value = state.rx_error.load(Ordering::Relaxed); value |= (data.0 >> 8) as u8; state.rx_error.store(value, 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 = data.data(); n_read += 1; } if n_read > 0 { rx_writer.push_done(n_read); state.rx_waker.wake(); } else if error { state.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 state.rx_buf.is_full() || error { r.cpu_int(0).imask().modify(|w| { w.set_rxint(false); w.set_rtout(false); }); } } if int.eot() { r.cpu_int(0).imask().modify(|w| { w.set_eot(false); }); r.cpu_int(0).iclr().write(|w| { w.set_eot(true); }); state.tx_waker.wake(); } // TX if state.tx_buf.is_available() { // SAFETY: TX must have been initialized if TXE is set. let mut tx_reader = unsafe { state.tx_buf.reader() }; let buf = tx_reader.pop_slice(); let mut n_written = 0; for tx_byte in buf.iter_mut() { let stat = r.stat().read(); if stat.txff() { break; } r.txdata().write(|w| { w.set_data(*tx_byte); }); n_written += 1; } if n_written > 0 { // EOT will wake. r.cpu_int(0).imask().modify(|w| { w.set_eot(true); }); tx_reader.pop_done(n_written); } } // Clear TX and error interrupt flags // RX interrupt flags are cleared by writing to ICLR. let mis = r.cpu_int(0).mis().read(); r.cpu_int(0).iclr().write(|w| { w.set_nerr(mis.nerr()); w.set_frmerr(mis.frmerr()); w.set_parerr(mis.parerr()); w.set_brkerr(mis.brkerr()); w.set_ovrerr(mis.ovrerr()); }); // Errors if mis.nerr() { warn!("Noise error"); } if mis.frmerr() { warn!("Framing error"); } if mis.parerr() { warn!("Parity error"); } if mis.brkerr() { warn!("Break error"); } if mis.ovrerr() { warn!("Overrun error"); } } ================================================ FILE: embassy-mspm0/src/uart/mod.rs ================================================ #![macro_use] mod buffered; use core::marker::PhantomData; use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; pub use buffered::*; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::PeripheralType; use crate::Peri; use crate::gpio::{AnyPin, PfType, Pull, SealedPin}; use crate::interrupt::{Interrupt, InterruptExt}; use crate::mode::{Blocking, Mode}; use crate::pac::uart::{Uart as Regs, vals}; /// The clock source for the UART. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClockSel { /// Use the low frequency clock. /// /// The LFCLK runs at 32.768 kHz. LfClk, /// Use the middle frequency clock. /// /// The MCLK runs at 4 MHz. MfClk, // BusClk, // BusClk depends on the timer's power domain. // This will be implemented later. } #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// The order of bits in byte. pub enum BitOrder { /// The most significant bit is first. MsbFirst, /// The least significant bit is first. LsbFirst, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Number of data bits pub enum DataBits { /// 5 Data Bits DataBits5, /// 6 Data Bits DataBits6, /// 7 Data Bits DataBits7, /// 8 Data Bits DataBits8, } #[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 { /// One stop bit Stop1, /// Two stop bits Stop2, } #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Config Error pub enum ConfigError { /// Rx or Tx not enabled RxOrTxNotEnabled, /// The baud rate could not be configured with the given clocks. InvalidBaudRate, } #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Config pub struct Config { /// UART clock source. pub clock_source: ClockSel, /// 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, /// The order of bits in a transmitted/received byte. pub msb_order: BitOrder, /// If true: the `TX` is internally connected to `RX`. pub loop_back_enable: bool, // TODO: Pending way to check if uart is extended // /// If true: [manchester coding] is used. // /// // /// [manchester coding]: https://en.wikipedia.org/wiki/Manchester_code // pub manchester: bool, // TODO: majority voting /// If true: the built-in FIFO is enabled. pub fifo_enable: bool, // TODO: glitch suppression /// If true: invert TX pin signal values (VDD = 0/mark, Gnd = 1/idle). pub invert_tx: bool, /// If true: invert RX pin signal values (VDD = 0/mark, Gnd = 1/idle). pub invert_rx: bool, /// If true: invert RTS pin signal values (VDD = 0/mark, Gnd = 1/idle). pub invert_rts: bool, /// If true: invert CTS pin signal values (VDD = 0/mark, Gnd = 1/idle). pub invert_cts: bool, /// Set the pull configuration for the TX pin. pub tx_pull: Pull, /// Set the pull configuration for the RX pin. pub rx_pull: Pull, /// Set the pull configuration for the RTS pin. pub rts_pull: Pull, /// Set the pull configuration for the CTS pin. pub cts_pull: Pull, } impl Default for Config { fn default() -> Self { Self { clock_source: ClockSel::MfClk, baudrate: 115200, data_bits: DataBits::DataBits8, stop_bits: StopBits::Stop1, parity: Parity::ParityNone, // hardware default msb_order: BitOrder::LsbFirst, loop_back_enable: false, // manchester: false, fifo_enable: false, invert_tx: false, invert_rx: false, invert_rts: false, invert_cts: false, tx_pull: Pull::None, rx_pull: Pull::None, rts_pull: Pull::None, cts_pull: Pull::None, } } } /// 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.set_config(config) } } /// Serial error #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { Framing, Noise, Overrun, Parity, Break, } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let message = match self { Self::Framing => "Framing Error", Self::Noise => "Noise Error", Self::Overrun => "RX Buffer Overrun", Self::Parity => "Parity Check Error", Self::Break => "Break Error", }; write!(f, "{}", message) } } impl core::error::Error for Error {} impl embedded_io::Error for Error { fn kind(&self) -> embedded_io::ErrorKind { embedded_io::ErrorKind::Other } } /// 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. pub struct UartRx<'d, M: Mode> { info: &'static Info, state: &'static State, rx: Option>, rts: Option>, _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> 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. pub fn new_blocking( peri: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config, ) -> Result { Self::new_inner(peri, new_pin!(rx, config.rx_pf()), None, config) } /// Create a new rx-only UART with a request-to-send pin pub fn new_blocking_with_rts( peri: Peri<'d, T>, rx: Peri<'d, impl RxPin>, rts: Peri<'d, impl RtsPin>, config: Config, ) -> Result { Self::new_inner( peri, new_pin!(rx, config.rx_pf()), new_pin!(rts, config.rts_pf()), config, ) } } impl<'d, M: Mode> UartRx<'d, M> { /// Perform a blocking read into `buffer` pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { let r = self.info.regs; for b in buffer { // Wait if nothing has arrived yet. while r.stat().read().rxfe() {} // Prevent the compiler from reading from buffer too early compiler_fence(Ordering::Acquire); *b = read_with_error(r)?; } Ok(()) } /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { if let Some(ref rx) = self.rx { rx.update_pf(config.rx_pf()); } if let Some(ref rts) = self.rts { rts.update_pf(config.rts_pf()); } reconfigure(self.info, self.state, config) } /// Set baudrate pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate) } } 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()); } } /// 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, tx: Option>, cts: 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> { reconfigure(self.info, self.state, config) } } 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. pub fn new_blocking( peri: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config, ) -> Result { Self::new_inner(peri, new_pin!(tx, config.tx_pf()), None, config) } /// Create a new blocking tx-only UART with a clear-to-send pin pub fn new_blocking_with_cts( peri: Peri<'d, T>, tx: Peri<'d, impl TxPin>, cts: Peri<'d, impl CtsPin>, config: Config, ) -> Result { Self::new_inner( peri, new_pin!(tx, config.tx_pf()), new_pin!(cts, config.cts_pf()), config, ) } } impl<'d, M: Mode> UartTx<'d, M> { /// Perform a blocking UART write pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = self.info.regs; for &b in buffer { // Wait if there is no space while !r.stat().read().txfe() {} // Prevent the compiler from writing to buffer too early compiler_fence(Ordering::Release); r.txdata().write(|w| { w.set_data(b); }); } Ok(()) } /// Block until transmission complete pub fn blocking_flush(&mut self) -> Result<(), Error> { let r = self.info.regs; // Wait until TX fifo/buffer is empty while r.stat().read().txfe() {} Ok(()) } /// Send break character pub fn send_break(&self) { let r = self.info.regs; r.lcrh().modify(|w| { w.set_brk(true); }); } /// Check if UART is busy. pub fn busy(&self) -> bool { busy(self.info.regs) } /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { if let Some(ref tx) = self.tx { tx.update_pf(config.tx_pf()); } if let Some(ref cts) = self.cts { cts.update_pf(config.cts_pf()); } reconfigure(self.info, self.state, config) } /// Set baudrate pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), 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()); } } impl<'d> Uart<'d, Blocking> { /// Create a new blocking bidirectional UART. pub fn new_blocking( peri: Peri<'d, T>, rx: Peri<'d, impl RxPin>, tx: Peri<'d, impl TxPin>, config: Config, ) -> Result { Self::new_inner( peri, new_pin!(rx, config.rx_pf()), new_pin!(tx, config.tx_pf()), None, None, config, ) } /// Create a new bidirectional UART with request-to-send and clear-to-send pins pub fn new_blocking_with_rtscts( peri: Peri<'d, T>, rx: Peri<'d, impl RxPin>, tx: Peri<'d, impl TxPin>, rts: Peri<'d, impl RtsPin>, cts: Peri<'d, impl CtsPin>, config: Config, ) -> Result { Self::new_inner( peri, new_pin!(rx, config.rx_pf()), new_pin!(tx, config.tx_pf()), new_pin!(rts, config.rts_pf()), new_pin!(cts, config.cts_pf()), config, ) } } impl<'d, M: Mode> Uart<'d, M> { /// 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() } /// Check if UART is busy. pub fn busy(&self) -> bool { self.tx.busy() } /// 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) } /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { self.tx.set_config(config)?; self.rx.set_config(config) } /// Send break character pub fn send_break(&self) { self.tx.send_break(); } /// Set baudrate pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { set_baudrate(&self.tx.info, self.tx.state.clock.load(Ordering::Relaxed), baudrate) } } /// Peripheral instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType { type Interrupt: crate::interrupt::typelevel::Interrupt; } /// UART `TX` pin trait pub trait TxPin: crate::gpio::Pin { /// Get the PF number needed to use this pin as `TX`. fn pf_num(&self) -> u8; } /// UART `RX` pin trait pub trait RxPin: crate::gpio::Pin { /// Get the PF number needed to use this pin as `RX`. fn pf_num(&self) -> u8; } /// UART `CTS` pin trait pub trait CtsPin: crate::gpio::Pin { /// Get the PF number needed to use this pin as `CTS`. fn pf_num(&self) -> u8; } /// UART `RTS` pin trait pub trait RtsPin: crate::gpio::Pin { /// Get the PF number needed to use this pin as `RTS`. fn pf_num(&self) -> u8; } // ==== IMPL types ==== pub(crate) struct Info { pub(crate) regs: Regs, pub(crate) interrupt: Interrupt, } pub(crate) struct State { /// The clock rate of the UART in Hz. clock: AtomicU32, } impl State { pub const fn new() -> Self { Self { clock: AtomicU32::new(0), } } } impl<'d, M: Mode> UartRx<'d, M> { fn new_inner( _peri: Peri<'d, T>, rx: Option>, rts: Option>, config: Config, ) -> Result { let mut this = Self { info: T::info(), state: T::state(), rx, rts, _phantom: PhantomData, }; this.enable_and_configure(&config)?; Ok(this) } fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { let info = self.info; enable(info.regs); configure(info, self.state, config, true, self.rts.is_some(), false, false)?; Ok(()) } } impl<'d, M: Mode> UartTx<'d, M> { fn new_inner( _peri: Peri<'d, T>, tx: Option>, cts: Option>, config: Config, ) -> Result { let mut this = Self { info: T::info(), state: T::state(), tx, cts, _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; enable(info.regs); configure(info, state, config, false, false, true, self.cts.is_some())?; Ok(()) } } impl<'d, M: Mode> Uart<'d, M> { fn new_inner( _peri: Peri<'d, T>, rx: Option>, tx: Option>, rts: Option>, cts: Option>, config: Config, ) -> Result { let info = T::info(); let state = T::state(); let mut this = Self { tx: UartTx { info, state, tx, cts, _phantom: PhantomData, }, rx: UartRx { info, state, rx, rts, _phantom: PhantomData, }, }; 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; enable(info.regs); configure( info, state, config, true, self.rx.rts.is_some(), true, self.tx.cts.is_some(), )?; info.interrupt.unpend(); unsafe { info.interrupt.enable() }; Ok(()) } } impl Config { fn tx_pf(&self) -> PfType { PfType::output(self.tx_pull, self.invert_tx) } fn rx_pf(&self) -> PfType { PfType::input(self.rx_pull, self.invert_rx) } fn rts_pf(&self) -> PfType { PfType::output(self.rts_pull, self.invert_rts) } fn cts_pf(&self) -> PfType { PfType::input(self.rts_pull, self.invert_rts) } } fn enable(regs: Regs) { let gprcm = regs.gprcm(0); gprcm.rstctl().write(|w| { w.set_resetstkyclr(true); w.set_resetassert(true); w.set_key(vals::ResetKey::KEY); }); gprcm.pwren().write(|w| { w.set_enable(true); w.set_key(vals::PwrenKey::KEY); }); } fn configure( info: &Info, state: &State, config: &Config, enable_rx: bool, enable_rts: bool, enable_tx: bool, enable_cts: bool, ) -> Result<(), ConfigError> { let r = info.regs; if !enable_rx && !enable_tx { return Err(ConfigError::RxOrTxNotEnabled); } // SLAU846B says that clocks should be enabled before disabling the uart. r.clksel().write(|w| match config.clock_source { ClockSel::LfClk => { w.set_lfclk_sel(true); w.set_mfclk_sel(false); w.set_busclk_sel(false); } ClockSel::MfClk => { w.set_mfclk_sel(true); w.set_lfclk_sel(false); w.set_busclk_sel(false); } }); let clock = match config.clock_source { ClockSel::LfClk => 32768, ClockSel::MfClk => 4_000_000, }; state.clock.store(clock, Ordering::Relaxed); info.regs.ctl0().modify(|w| { w.set_lbe(config.loop_back_enable); // Errata UART_ERR_02, must set RXE to allow use of EOT. w.set_rxe(enable_rx | enable_tx); w.set_txe(enable_tx); // RXD_OUT_EN and TXD_OUT_EN? w.set_menc(false); w.set_mode(vals::Mode::UART); w.set_rtsen(enable_rts); w.set_ctsen(enable_cts); // oversampling is set later w.set_fen(config.fifo_enable); // TODO: config w.set_majvote(false); w.set_msbfirst(matches!(config.msb_order, BitOrder::MsbFirst)); }); info.regs.ifls().modify(|w| { // TODO: Need power domain info for other options. w.set_txiflsel(vals::Iflssel::AT_LEAST_ONE); w.set_rxiflsel(vals::Iflssel::AT_LEAST_ONE); }); info.regs.lcrh().modify(|w| { let eps = if matches!(config.parity, Parity::ParityEven) { vals::Eps::EVEN } else { vals::Eps::ODD }; let wlen = match config.data_bits { DataBits::DataBits5 => vals::Wlen::DATABIT5, DataBits::DataBits6 => vals::Wlen::DATABIT6, DataBits::DataBits7 => vals::Wlen::DATABIT7, DataBits::DataBits8 => vals::Wlen::DATABIT8, }; // Used in LIN mode only w.set_brk(false); w.set_pen(config.parity != Parity::ParityNone); w.set_eps(eps); w.set_stp2(matches!(config.stop_bits, StopBits::Stop2)); w.set_wlen(wlen); // appears to only be used in RS-485 mode. w.set_sps(false); // IDLE pattern? w.set_sendidle(false); // ignore extdir_setup and extdir_hold, only used in RS-485 mode. }); set_baudrate_inner(info.regs, clock, config.baudrate)?; r.ctl0().modify(|w| { w.set_enable(true); }); Ok(()) } fn reconfigure(info: &Info, state: &State, config: &Config) -> Result<(), ConfigError> { info.interrupt.disable(); let r = info.regs; let ctl0 = r.ctl0().read(); configure(info, state, config, ctl0.rxe(), ctl0.rtsen(), ctl0.txe(), ctl0.ctsen())?; info.interrupt.unpend(); unsafe { info.interrupt.enable() }; Ok(()) } /// Set the baud rate and clock settings. /// /// This should be done relatively late during configuration since some clock settings are invalid depending on mode. fn set_baudrate(info: &Info, clock: u32, baudrate: u32) -> Result<(), ConfigError> { let r = info.regs; info.interrupt.disable(); // Programming baud rate requires that the peripheral is disabled critical_section::with(|_cs| { r.ctl0().modify(|w| { w.set_enable(false); }); }); // Wait for end of transmission per suggestion in SLAU 845 section 18.3.28 while !r.stat().read().txfe() {} set_baudrate_inner(r, clock, baudrate)?; critical_section::with(|_cs| { r.ctl0().modify(|w| { w.set_enable(true); }); }); info.interrupt.unpend(); unsafe { info.interrupt.enable() }; Ok(()) } fn set_baudrate_inner(regs: Regs, clock: u32, baudrate: u32) -> Result<(), ConfigError> { // Quoting SLAU846 section 18.2.3.4: // "When IBRD = 0, FBRD is ignored and no data gets transferred by the UART." const MIN_IBRD: u16 = 1; // FBRD can be 0 // FBRD is at most a 6-bit number. const MAX_FBRD: u8 = 2_u8.pow(6); const DIVS: [(u8, vals::Clkdiv); 8] = [ (1, vals::Clkdiv::DIV_BY_1), (2, vals::Clkdiv::DIV_BY_2), (3, vals::Clkdiv::DIV_BY_3), (4, vals::Clkdiv::DIV_BY_4), (5, vals::Clkdiv::DIV_BY_5), (6, vals::Clkdiv::DIV_BY_6), (7, vals::Clkdiv::DIV_BY_7), (8, vals::Clkdiv::DIV_BY_8), ]; // Quoting from SLAU 846 section 18.2.3.4: // "Select oversampling by 3 or 8 to achieve higher speed with UARTclk/8 or UARTclk/3. In this case // the receiver tolerance to clock deviation is reduced." // // "Select oversampling by 16 to increase the tolerance of the receiver to clock deviations. The // maximum speed is limited to UARTclk/16." // // Based on these requirements, prioritize higher oversampling first to increase tolerance to clock // deviation. If no valid BRD value can be found satisifying the highest sample rate, then reduce // sample rate until valid parameters are found. const OVS: [(u8, vals::Hse); 3] = [(16, vals::Hse::OVS16), (8, vals::Hse::OVS8), (3, vals::Hse::OVS3)]; // 3x oversampling is not supported with manchester coding, DALI or IrDA. let x3_invalid = { let ctl0 = regs.ctl0().read(); let irctl = regs.irctl().read(); ctl0.menc() || matches!(ctl0.mode(), vals::Mode::DALI) || irctl.iren() }; let mut found = None; 'outer: for &(oversampling, hse_value) in &OVS { if matches!(hse_value, vals::Hse::OVS3) && x3_invalid { continue; } // Verify that the selected oversampling does not require a clock faster than what the hardware // is provided. let Some(min_clock) = baudrate.checked_mul(oversampling as u32) else { trace!( "{}x oversampling would cause overflow for clock: {} Hz", oversampling, clock ); continue; }; if min_clock > clock { trace!("{} oversampling is too high for clock: {} Hz", oversampling, clock); continue; } for &(div, div_value) in &DIVS { trace!( "Trying div: {}, oversampling {} for {} baud", div, oversampling, baudrate ); let Some((ibrd, fbrd)) = calculate_brd(clock, div, baudrate, oversampling) else { trace!("Calculating BRD overflowed: trying another divider"); continue; }; if ibrd < MIN_IBRD || fbrd > MAX_FBRD { trace!("BRD was invalid: trying another divider"); continue; } found = Some((hse_value, div_value, ibrd, fbrd)); break 'outer; } } let Some((hse, div, ibrd, fbrd)) = found else { return Err(ConfigError::InvalidBaudRate); }; regs.clkdiv().write(|w| { w.set_ratio(div); }); regs.ibrd().write(|w| { w.set_divint(ibrd); }); regs.fbrd().write(|w| { w.set_divfrac(fbrd); }); regs.ctl0().modify(|w| { w.set_hse(hse); }); Ok(()) } /// Calculate the integer and fractional parts of the `BRD` value. /// /// Returns [`None`] if calculating this results in overflows. /// /// Values returned are `(ibrd, fbrd)` fn calculate_brd(clock: u32, div: u8, baud: u32, oversampling: u8) -> Option<(u16, u8)> { use fixed::types::U26F6; // Calculate BRD according to SLAU 846 section 18.2.3.4. // // BRD is a 22-bit value with 16 integer bits and 6 fractional bits. // // uart_clock = clock / div // brd = ibrd.fbrd = uart_clock / (oversampling * baud)" // // It is tempting to rearrange the equation such that there is only a single division in // order to reduce error. However this is wrong since the denominator ends up being too // small to represent in 6 fraction bits. This means that FBRD would always be 0. // // Calculations are done in a U16F6 format. However the fixed crate has no such representation. // U26F6 is used since it has the same number of fractional bits and we verify at the end that // the integer part did not overflow. let clock = U26F6::from_num(clock); let div = U26F6::from_num(div); let oversampling = U26F6::from_num(oversampling); let baud = U26F6::from_num(baud); let uart_clock = clock.checked_div(div)?; // oversampling * baud let denom = oversampling.checked_mul(baud)?; // uart_clock / (oversampling * baud) let brd = uart_clock.checked_div(denom)?; // Checked is used to determine overflow in the 10 most singificant bits since the // actual representation of BRD is U16F6. let ibrd = brd.checked_to_num::()?; // We need to scale FBRD's representation to an integer. let fbrd_scale = U26F6::from_num(2_u32.checked_pow(U26F6::FRAC_NBITS)?); // It is suggested that 0.5 is added to ensure that any fractional parts round up to the next // integer. If it doesn't round up then it'll get discarded which is okay. let half = U26F6::from_num(1) / U26F6::from_num(2); // fbrd = INT(((FRAC(BRD) * 64) + 0.5)) let fbrd = brd .frac() .checked_mul(fbrd_scale)? .checked_add(half)? .checked_to_num::()?; Some((ibrd, fbrd)) } fn read_with_error(r: Regs) -> Result { let rx = r.rxdata().read(); if rx.frmerr() { return Err(Error::Framing); } else if rx.parerr() { return Err(Error::Parity); } else if rx.brkerr() { return Err(Error::Break); } else if rx.ovrerr() { return Err(Error::Overrun); } else if rx.nerr() { return Err(Error::Noise); } Ok(rx.data()) } /// This function assumes CTL0.ENABLE is set (for errata cases). fn busy(r: Regs) -> bool { // Errata UART_ERR_08 if cfg!(any( mspm0g151x, mspm0g351x, mspm0l110x, mspm0l130x, mspm0l134x, mspm0c110x, )) { let stat = r.stat().read(); // "Poll TXFIFO status and the CTL0.ENABLE register bit to identify BUSY status." !stat.txfe() } else { r.stat().read().busy() } } // TODO: Implement when dma uart is implemented. fn dma_enabled(_r: Regs) -> bool { false } pub(crate) trait SealedInstance { fn info() -> &'static Info; fn state() -> &'static State; fn buffered_state() -> &'static BufferedState; } macro_rules! impl_uart_instance { ($instance: ident) => { impl crate::uart::SealedInstance for crate::peripherals::$instance { fn info() -> &'static crate::uart::Info { use crate::interrupt::typelevel::Interrupt; use crate::uart::Info; const INFO: Info = Info { regs: crate::pac::$instance, interrupt: crate::interrupt::typelevel::$instance::IRQ, }; &INFO } fn state() -> &'static crate::uart::State { use crate::uart::State; static STATE: State = State::new(); &STATE } fn buffered_state() -> &'static crate::uart::BufferedState { use crate::uart::BufferedState; static STATE: BufferedState = BufferedState::new(); &STATE } } impl crate::uart::Instance for crate::peripherals::$instance { type Interrupt = crate::interrupt::typelevel::$instance; } }; } macro_rules! impl_uart_tx_pin { ($instance: ident, $pin: ident, $pf: expr) => { impl crate::uart::TxPin for crate::peripherals::$pin { fn pf_num(&self) -> u8 { $pf } } }; } macro_rules! impl_uart_rx_pin { ($instance: ident, $pin: ident, $pf: expr) => { impl crate::uart::RxPin for crate::peripherals::$pin { fn pf_num(&self) -> u8 { $pf } } }; } macro_rules! impl_uart_cts_pin { ($instance: ident, $pin: ident, $pf: expr) => { impl crate::uart::CtsPin for crate::peripherals::$pin { fn pf_num(&self) -> u8 { $pf } } }; } macro_rules! impl_uart_rts_pin { ($instance: ident, $pin: ident, $pf: expr) => { impl crate::uart::RtsPin for crate::peripherals::$pin { fn pf_num(&self) -> u8 { $pf } } }; } #[cfg(test)] mod tests { use super::calculate_brd; /// This is a smoke test based on the example in SLAU 846 section 18.2.3.4. #[test] fn datasheet() { let brd = calculate_brd(40_000_000, 1, 19200, 16); assert!(matches!(brd, Some((130, 13)))); } } ================================================ FILE: embassy-mspm0/src/wwdt.rs ================================================ //! Window Watchdog Timer (WWDT) driver. //! //! This HAL implements a basic window watchdog timer with handles. #![macro_use] use embassy_hal_internal::PeripheralType; use crate::Peri; use crate::pac::wwdt::{Wwdt as Regs, vals}; use crate::pac::{self}; /// Possible watchdog timeout values. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Timeout { USec1950, USec3910, USec5860, USec7810, USec9770, USec11720, USec13670, USec15630, USec23440, USec31250, USec32250, USec39060, USec46880, USec54690, USec62500, USec93750, USec125000, USec156250, USec187500, USec218750, MSec130, MSec250, MSec380, MSec500, MSec630, MSec750, MSec880, Sec1, Sec2, Sec3, Sec4, Sec5, Sec6, Sec7, Sec8, Sec16, Sec24, Sec32, Sec40, Sec48, Sec56, Sec64, Sec128, // 2.13 min Sec192, // 3.20 min Sec256, // 4.27 min Sec320, // 5.33 min Sec384, // 6.40 min Sec448, // 7.47 min Sec512, // 8.53 min Sec1024, // 17.07 min Sec2048, // 34.13 min Sec3072, // 51.20 min Sec4096, // 68.27 min Sec5120, // 85.33 min Sec6144, // 102.40 min Sec7168, // 119.47 min Sec8192, // 136.53 min } impl Timeout { fn get_period(self) -> vals::Per { match self { // period count is 2**25 Self::Sec1024 | Self::Sec2048 | Self::Sec3072 | Self::Sec4096 | Self::Sec5120 | Self::Sec6144 | Self::Sec7168 | Self::Sec8192 => vals::Per::EN_25, // period count is 2**21 Self::Sec64 | Self::Sec128 | Self::Sec192 | Self::Sec256 | Self::Sec320 | Self::Sec384 | Self::Sec448 | Self::Sec512 => vals::Per::EN_21, // period count is 2**18 Self::Sec8 | Self::Sec16 | Self::Sec24 | Self::Sec32 | Self::Sec40 | Self::Sec48 | Self::Sec56 => { vals::Per::EN_18 } // period count is 2**15 Self::Sec1 | Self::Sec2 | Self::Sec3 | Self::Sec4 | Self::Sec5 | Self::Sec6 | Self::Sec7 => { vals::Per::EN_15 } // period count is 2**12 Self::MSec130 | Self::MSec250 | Self::MSec380 | Self::MSec500 | Self::MSec630 | Self::MSec750 | Self::MSec880 => vals::Per::EN_12, // period count is 2**10 Self::USec31250 | Self::USec62500 | Self::USec93750 | Self::USec125000 | Self::USec156250 | Self::USec187500 | Self::USec218750 => vals::Per::EN_10, // period count is 2**8 Self::USec7810 | Self::USec15630 | Self::USec23440 | Self::USec32250 | Self::USec39060 | Self::USec46880 | Self::USec54690 => vals::Per::EN_8, // period count is 2**6 Self::USec1950 | Self::USec3910 | Self::USec5860 | Self::USec9770 | Self::USec11720 | Self::USec13670 => { vals::Per::EN_6 } } } fn get_clkdiv(self) -> u8 { match self { // divide by 1 Self::USec1950 | Self::USec7810 | Self::USec31250 | Self::MSec130 | Self::Sec1 | Self::Sec8 | Self::Sec64 | Self::Sec1024 => 0u8, // divide by 2 Self::USec3910 | Self::USec15630 | Self::USec62500 | Self::MSec250 | Self::Sec2 | Self::Sec16 | Self::Sec128 | Self::Sec2048 => 1u8, // divide by 3 Self::USec5860 | Self::USec23440 | Self::USec93750 | Self::MSec380 | Self::Sec3 | Self::Sec24 | Self::Sec192 | Self::Sec3072 => 2u8, // divide by 4 Self::USec32250 | Self::USec125000 | Self::MSec500 | Self::Sec4 | Self::Sec32 | Self::Sec256 | Self::Sec4096 => 3u8, // divide by 5 Self::USec9770 | Self::USec39060 | Self::USec156250 | Self::MSec630 | Self::Sec5 | Self::Sec40 | Self::Sec320 | Self::Sec5120 => 4u8, // divide by 6 Self::USec11720 | Self::USec46880 | Self::USec187500 | Self::MSec750 | Self::Sec6 | Self::Sec48 | Self::Sec384 | Self::Sec6144 => 5u8, // divide by 7 Self::USec13670 | Self::USec54690 | Self::USec218750 | Self::MSec880 | Self::Sec7 | Self::Sec56 | Self::Sec448 | Self::Sec7168 => 6u8, // divide by 8 Self::Sec512 | Self::Sec8192 => 7u8, } } } /// Timeout percentage that is treated as "too early" and generates violation. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClosedWindowPercentage { // window period is not used Zero, // 12.5% percents Twelve, // 18.75% percents Eighteen, // 25% percents TwentyFive, // 50% percents Fifty, // 75% percents SeventyFive, // 81.25% percents EightyOne, // 87.5% percents EightySeven, } impl ClosedWindowPercentage { fn get_native_size(self) -> vals::Window { match self { Self::Zero => vals::Window::SIZE_0, Self::Twelve => vals::Window::SIZE_12, Self::Eighteen => vals::Window::SIZE_18, Self::TwentyFive => vals::Window::SIZE_25, Self::Fifty => vals::Window::SIZE_50, Self::SeventyFive => vals::Window::SIZE_75, Self::EightyOne => vals::Window::SIZE_81, Self::EightySeven => vals::Window::SIZE_87, } } } #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Watchdog Config pub struct Config { /// Watchdog timeout pub timeout: Timeout, /// closed window percentage pub closed_window: ClosedWindowPercentage, } impl Default for Config { fn default() -> Self { Self { timeout: Timeout::Sec1, closed_window: ClosedWindowPercentage::Zero, } } } pub struct Watchdog { regs: &'static Regs, } impl Watchdog { /// Watchdog initialization. pub fn new(_instance: Peri, config: Config) -> Self { // Init power for watchdog T::regs().gprcm(0).rstctl().write(|w| { w.set_resetstkyclr(true); w.set_resetassert(true); w.set_key(vals::ResetKey::KEY); }); // Enable power for watchdog T::regs().gprcm(0).pwren().write(|w| { w.set_enable(true); w.set_key(vals::PwrenKey::KEY); }); // init delay, 16 cycles cortex_m::asm::delay(16); critical_section::with(|_| { // make sure watchdog triggers BOOTRST pac::SYSCTL.systemcfg().modify(|w| { if *T::regs() == pac::WWDT0 { w.set_wwdtlp0rstdis(false); } #[cfg(any(mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x))] if *T::regs() == pac::WWDT1 { w.set_wwdtlp1rstdis(false); } }); }); //init watchdog T::regs().wwdtctl0().write(|w| { w.set_clkdiv(config.timeout.get_clkdiv()); w.set_per(config.timeout.get_period()); w.set_mode(vals::Mode::WINDOW); w.set_window0(config.closed_window.get_native_size()); w.set_window1(vals::Window::SIZE_0); w.set_key(vals::Wwdtctl0Key::KEY); }); // Set Window0 as active window T::regs().wwdtctl1().write(|w| { w.set_winsel(vals::Winsel::WIN0); w.set_key(vals::Wwdtctl1Key::KEY); }); Self { regs: T::regs() } } /// Pet (reload, refresh) the watchdog. pub fn pet(&mut self) { self.regs.wwdtcntrst().write(|w| { w.set_restart(vals::WwdtcntrstRestart::RESTART); }); } } pub(crate) trait SealedInstance { fn regs() -> &'static Regs; } /// WWDT instance trait #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType {} macro_rules! impl_wwdt_instance { ($instance: ident) => { impl crate::wwdt::SealedInstance for crate::peripherals::$instance { fn regs() -> &'static crate::pac::wwdt::Wwdt { &crate::pac::$instance } } impl crate::wwdt::Instance for crate::peripherals::$instance {} }; } ================================================ FILE: embassy-net/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.9.0 - 2026-03-10 - raw: Removed unnecessary Driver type parameter from `RawSocket::new` - `{UdpSocket, IcmpSocket}::send_to_with` support writing less than `max_size` into the buffer by returning the number of bytes written from the closure - Update embassy-sync 0.8.0 ## 0.8.0 - 2026-01-04 - tcp: Add `set_nagle_enabled()` to control TcpSocket nagle algorithm. - update to embedded-io 0.7 - update to embedded-nal 0.9 ## 0.7.1 - 2025-08-26 No unreleased changes yet... Quick, go send a PR! ## 0.7 - 2025-05-06 - don't infinite loop if udp::send methods receive a buffer too large to ever be sent - add ICMP sockets and a ping utility - configurable rate_limit for the ping utility - Feature match udp sockets ## 0.6 - 2025-01-05 - Make `Config` constructors `const` - The `std` feature has been removed - Updated `embassy-time` to v0.4 ## 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 ================================================ FILE: embassy-net/Cargo.toml ================================================ [package] name = "embassy-net" version = "0.9.0" edition = "2024" 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] build = [ {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "packet-trace", "proto-ipv4", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "multicast", "proto-ipv4", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dhcpv4", "dns", "medium-ethernet", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dhcpv4", "dhcpv4-hostname", "dns", "medium-ethernet", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dhcpv4", "dhcpv4-hostname", "dns", "medium-ethernet", "slaac", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "proto-ipv6", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "proto-ipv6", "slaac", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ieee802154", "proto-ipv6", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "medium-ieee802154", "proto-ipv6", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "proto-ipv4", "proto-ipv6", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ip", "proto-ipv4", "proto-ipv6", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "medium-ip", "proto-ipv4", "proto-ipv6", "tcp", "udp"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "dns", "medium-ethernet", "medium-ieee802154", "medium-ip", "proto-ipv4", "proto-ipv6", "tcp", "udp"]}, # Xtensa builds {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv4", "medium-ethernet", "packet-trace"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv4", "multicast", "medium-ethernet"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "dhcpv4", "medium-ethernet"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "dhcpv4", "medium-ethernet", "dhcpv4-hostname"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv6", "medium-ethernet"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv6", "medium-ieee802154"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv6", "medium-ethernet", "medium-ieee802154"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv6", "medium-ethernet"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv4", "proto-ipv6", "medium-ethernet"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv4", "proto-ipv6", "medium-ip"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv4", "proto-ipv6", "medium-ip", "medium-ethernet"]}, {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "tcp", "udp", "dns", "proto-ipv4", "proto-ipv6", "medium-ip", "medium-ethernet", "medium-ieee802154"]}, ] [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", "icmp", "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", "icmp", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] [features] ## Enable defmt defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "embassy-time/defmt", "heapless/defmt", "defmt?/ip_in_core"] ## Enable log log = ["dep:log"] ## 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 ICMP support icmp = ["smoltcp/socket-icmp"] ## ICMP echo request auto reply auto-icmp-echo-reply = ["smoltcp/auto-icmp-echo-reply"] ## 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"] ## Enable stateless address autoconfiguration for ipv6 slaac = ["proto-ipv6", "multicast", "smoltcp/proto-ipv6-slaac"] ## Enable smoltcp std feature (necessary if using "managed" crate std feature) std = ["smoltcp/std"] ## Enable smoltcp alloc feature (necessary if using "managed" crate alloc feature) alloc = ["smoltcp/alloc"] [dependencies] defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } smoltcp = { version = "0.13.0", default-features = false, features = [ "socket", "async", ] } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embedded-io-async = { version = "0.7.0" } managed = { version = "0.8.0", default-features = false, features = [ "map" ] } heapless = { version = "0.9", default-features = false } embedded-nal-async = "0.9.0" document-features = "0.2.7" ================================================ FILE: embassy-net/README.md ================================================ # 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. - [`embassy-nrf`](https://github.com/embassy-rs/embassy/tree/main/embassy-nrf) for IEEE 802.15.4 support on nrf chips. ## 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. ================================================ FILE: embassy-net/src/dns.rs ================================================ //! 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 } ================================================ FILE: embassy-net/src/driver_util.rs ================================================ 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 }) } } ================================================ FILE: embassy-net/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net/src/icmp.rs ================================================ //! ICMP sockets. use core::future::{Future, poll_fn}; use core::mem; use core::task::{Context, Poll}; use smoltcp::iface::{Interface, SocketHandle}; pub use smoltcp::phy::ChecksumCapabilities; use smoltcp::socket::icmp; pub use smoltcp::socket::icmp::{Endpoint as IcmpEndpoint, PacketMetadata}; use smoltcp::wire::IpAddress; #[cfg(feature = "proto-ipv4")] pub use smoltcp::wire::{Icmpv4Message, Icmpv4Packet, Icmpv4Repr}; #[cfg(feature = "proto-ipv6")] pub use smoltcp::wire::{Icmpv6Message, Icmpv6Packet, Icmpv6Repr}; use crate::Stack; /// Error returned by [`IcmpSocket::bind`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BindError { /// The socket was already open. InvalidState, /// The endpoint isn't specified InvalidEndpoint, /// No route to host. NoRoute, } /// Error returned by [`IcmpSocket::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, /// There is not enough transmit buffer capacity to ever send this packet. PacketTooLarge, } /// Error returned by [`IcmpSocket::recv_from`]. #[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 ICMP socket. pub struct IcmpSocket<'a> { stack: Stack<'a>, handle: SocketHandle, } impl<'a> IcmpSocket<'a> { /// Create a new ICMP 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(icmp::Socket::new( icmp::PacketBuffer::new(rx_meta, rx_buffer), icmp::PacketBuffer::new(tx_meta, tx_buffer), )) }); Self { stack, handle } } /// Bind the socket to the given endpoint. pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> where T: Into, { let endpoint = endpoint.into(); if !endpoint.is_specified() { return Err(BindError::InvalidEndpoint); } match self.with_mut(|s, _| s.bind(endpoint)) { Ok(()) => Ok(()), Err(icmp::BindError::InvalidState) => Err(BindError::InvalidState), Err(icmp::BindError::Unaddressable) => Err(BindError::NoRoute), } } fn with(&self, f: impl FnOnce(&icmp::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 icmp::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 fn wait_recv_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_recv_ready(cx)) } /// 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 fn recv_from<'s>( &'s self, buf: &'s mut [u8], ) -> impl Future> + 's { poll_fn(|cx| self.poll_recv_from(buf, cx)) } /// 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(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), Err(icmp::RecvError::Exhausted) => { s.register_recv_waker(cx.waker()); Poll::Pending } }) } /// Dequeue a packet received from a remote endpoint and calls the provided function with the /// slice of the packet and the remote endpoint address and returns `Poll::Ready` with the /// function's returned value. /// /// **Note**: when the size of the provided buffer is smaller than the size of the payload, /// the packet is dropped and a `RecvError::Truncated` error is returned. pub async fn recv_from_with(&self, f: F) -> Result where F: FnOnce((&[u8], IpAddress)) -> R, { let mut f = Some(f); poll_fn(move |cx| { self.with_mut(|s, _| match s.recv() { Ok(x) => Poll::Ready(Ok(unwrap!(f.take())(x))), Err(icmp::RecvError::Exhausted) => { cx.waker().wake_by_ref(); Poll::Pending } Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), }) }) .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 fn wait_send_ready(&self) -> impl Future + '_ { poll_fn(|cx| self.poll_send_ready(cx)) } /// 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. /// /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)` /// /// 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: IpAddress = 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. /// /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))` /// /// 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, { // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer. let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len()); if send_capacity_too_small { return Poll::Ready(Err(SendError::PacketTooLarge)); } self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint.into()) { // Entire datagram has been sent Ok(()) => Poll::Ready(Ok(())), Err(icmp::SendError::BufferFull) => { s.register_send_waker(cx.waker()); Poll::Pending } Err(icmp::SendError::Unaddressable) => { // If no sender/outgoing port is specified, there is not really "no route" if s.is_open() { Poll::Ready(Err(SendError::NoRoute)) } else { Poll::Ready(Err(SendError::SocketNotBound)) } } }) } /// Enqueue a packet to be sent to a given remote address with a zero-copy function. /// /// This method will wait until the buffer can fit the requested size before /// passing it to the closure. The closure returns the number of bytes /// written into the buffer. pub async fn send_to_with(&mut self, max_size: usize, remote_endpoint: T, f: F) -> Result where T: Into, F: FnOnce(&mut [u8]) -> (usize, R), { // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer. let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < max_size); if send_capacity_too_small { return Err(SendError::PacketTooLarge); } let mut f = Some(f); let remote_endpoint = remote_endpoint.into(); poll_fn(move |cx| { self.with_mut(|s, _| { let mut ret = None; match s.send_with(max_size, remote_endpoint, |buf| { let (size, r) = unwrap!(f.take())(buf); ret = Some(r); size }) { Ok(_n) => Poll::Ready(Ok(unwrap!(ret))), Err(icmp::SendError::BufferFull) => { s.register_send_waker(cx.waker()); Poll::Pending } Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)), } }) }) .await } /// Flush the socket. /// /// This method will wait until the socket is flushed. pub fn flush(&mut self) -> impl Future + '_ { poll_fn(|cx| { self.with_mut(|s, _| { if s.send_queue() == 0 { Poll::Ready(()) } else { s.register_send_waker(cx.waker()); Poll::Pending } }) }) } /// Check whether the socket is open. pub fn is_open(&self) -> bool { self.with(|s, _| s.is_open()) } /// 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()) } /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. pub fn hop_limit(&self) -> Option { self.with(|s, _| s.hop_limit()) } /// 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 IcmpSocket<'_> { fn drop(&mut self) { self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } pub mod ping { //! Ping utilities. //! //! This module allows for an easy ICMP Echo message interface used to //! ping devices with an [ICMP Socket](IcmpSocket). //! //! ## Usage //! //! ``` //! use core::net::Ipv4Addr; //! use core::str::FromStr; //! //! use embassy_net::icmp::ping::{PingManager, PingParams}; //! use embassy_net::icmp::PacketMetadata; //! //! let mut rx_buffer = [0; 256]; //! let mut tx_buffer = [0; 256]; //! let mut rx_meta = [PacketMetadata::EMPTY]; //! let mut tx_meta = [PacketMetadata::EMPTY]; //! //! let mut ping_manager = PingManager::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); //! let addr = "192.168.8.1"; //! let mut ping_params = PingParams::new(Ipv4Addr::from_str(addr).unwrap()); //! ping_params.set_payload(b"Hello, router!"); //! match ping_manager.ping(&ping_params).await { //! Ok(time) => info!("Ping time of {}: {}ms", addr, time.as_millis()), //! Err(ping_error) => warn!("{:?}", ping_error), //! }; //! ``` use core::net::IpAddr; #[cfg(feature = "proto-ipv6")] use core::net::Ipv6Addr; use embassy_time::{Duration, Instant, Timer, WithTimeout}; #[cfg(feature = "proto-ipv6")] use smoltcp::wire::IpAddress; #[cfg(feature = "proto-ipv6")] use smoltcp::wire::Ipv6Address; use super::*; /// Error returned by [`ping()`](PingManager::ping). #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PingError { /// The target did not respond. /// /// The packet was sent but the Reply packet has not been recieved /// in the timeout set by [`set_timeout()`](PingParams::set_timeout). DestinationHostUnreachable, /// The target has not been specified. InvalidTargetAddress, /// The source has not been specified (Ipv6 only). #[cfg(feature = "proto-ipv6")] InvalidSourceAddress, /// The socket could not queue the packet in the buffer. SocketSendTimeout, /// Container error for [`icmp::BindError`]. SocketBindError(BindError), /// Container error for [`icmp::SendError`]. SocketSendError(SendError), /// Container error for [`icmp::RecvError`]. SocketRecvError(RecvError), } /// Manages ICMP ping operations. /// /// This struct provides functionality to send ICMP echo requests (pings) to a specified target /// and measure the round-trip time for the requests. It supports both IPv4 and IPv6, depending /// on the enabled features. /// /// # Fields /// /// * `stack` - The network stack instance used for managing network operations. /// * `rx_meta` - Metadata buffer for receiving packets. /// * `rx_buffer` - Buffer for receiving packets. /// * `tx_meta` - Metadata buffer for transmitting packets. /// * `tx_buffer` - Buffer for transmitting packets. /// * `ident` - Identifier for the ICMP echo requests. /// /// # Methods /// /// * [`new`](PingManager::new) - Creates a new instance of `PingManager` with the specified stack and buffers. /// * [`ping`](PingManager::ping) - Sends ICMP echo requests to the specified target and returns the average round-trip time. pub struct PingManager<'d> { stack: Stack<'d>, rx_meta: &'d mut [PacketMetadata], rx_buffer: &'d mut [u8], tx_meta: &'d mut [PacketMetadata], tx_buffer: &'d mut [u8], ident: u16, } impl<'d> PingManager<'d> { /// Creates a new instance of [`PingManager`] with a [`Stack`] instance /// and the buffers used for RX and TX. /// /// **note**: This does not yet creates the ICMP socket. pub fn new( stack: Stack<'d>, rx_meta: &'d mut [PacketMetadata], rx_buffer: &'d mut [u8], tx_meta: &'d mut [PacketMetadata], tx_buffer: &'d mut [u8], ) -> Self { Self { stack, rx_meta, rx_buffer, tx_meta, tx_buffer, ident: 0, } } /// Sends ICMP echo requests to the specified target and returns the average round-trip time. /// /// # Arguments /// /// * `params` - Parameters for configuring the ping operation. /// /// # Returns /// /// * `Ok(Duration)` - The average round-trip time for the ping requests. /// * `Err(PingError)` - An error occurred during the ping operation. pub async fn ping<'a>(&mut self, params: &PingParams<'a>) -> Result { // Input validation if params.target().is_none() { return Err(PingError::InvalidTargetAddress); } #[cfg(feature = "proto-ipv6")] if params.target().unwrap().is_ipv6() && params.source().is_none() { return Err(PingError::InvalidSourceAddress); } // Increment the ident (wrapping u16) to respect standards self.ident = self.ident.wrapping_add(1u16); // Used to calculate the average duration let mut total_duration = Duration::default(); let mut num_of_durations = 0u16; // Increment the sequence number as per standards for seq_no in 0..params.count() { // Make sure each ping takes at least 1 second to respect standards let rate_limit_start = Instant::now(); // make a single ping // - shorts out errors // - select the ip version let ping_duration = match params.target.unwrap() { #[cfg(feature = "proto-ipv4")] IpAddress::Ipv4(_) => self.single_ping_v4(params, seq_no).await?, #[cfg(feature = "proto-ipv6")] IpAddress::Ipv6(_) => self.single_ping_v6(params, seq_no).await?, }; // safely add up the durations of each ping if let Some(dur) = total_duration.checked_add(ping_duration) { total_duration = dur; num_of_durations += 1; } // 1 sec min per ping let rate_limit_end = rate_limit_start.elapsed(); if rate_limit_end <= params.rate_limit { Timer::after(params.rate_limit.checked_sub(rate_limit_end).unwrap()).await; } } // calculate and return the average duration Ok(total_duration.checked_div(num_of_durations as u32).unwrap()) } #[cfg(feature = "proto-ipv4")] fn create_repr_ipv4<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv4Repr<'b> { Icmpv4Repr::EchoRequest { ident: self.ident, seq_no, data: params.payload, } } #[cfg(feature = "proto-ipv6")] fn create_repr_ipv6<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv6Repr<'b> { Icmpv6Repr::EchoRequest { ident: self.ident, seq_no, data: params.payload, } } #[cfg(feature = "proto-ipv4")] async fn single_ping_v4(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result { let ping_repr = self.create_repr_ipv4(params, seq_no); // Create the socket and set hop limit and bind it to the endpoint with the ident let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer); socket.set_hop_limit(params.hop_limit); if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) { return Err(PingError::SocketBindError(e)); } // Helper func to fill the buffer when sending the ICMP packet fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv4Repr<'_>) -> Instant { let mut icmp_packet = Icmpv4Packet::new_unchecked(buf); ping_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default()); Instant::now() } // Send with timeout the ICMP packet filling it with the helper function let send_result = socket .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| { (buf.len(), fill_packet_buffer(buf, ping_repr)) }) .with_timeout(Duration::from_millis(100)) .await; // Filter and translate potential errors from sending the packet let now = match send_result { Ok(send_result) => match send_result { Ok(i) => i, Err(e) => return Err(PingError::SocketSendError(e)), }, Err(_) => return Err(PingError::SocketSendTimeout), }; // Helper function for the recieve helper function to validate the echo reply fn filter_pong(buf: &[u8], seq_no: u16) -> bool { let pong_packet = match Icmpv4Packet::new_checked(buf) { Ok(pak) => pak, Err(_) => return false, }; pong_packet.echo_seq_no() == seq_no } // Helper function to recieve and return the correct echo reply when it finds it async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await { Ok(b) => !b, Err(e) => return Err(PingError::SocketRecvError(e)), } {} Ok(()) } // Calls the recieve helper function with a timeout match recv_pong(&socket, seq_no).with_timeout(params.timeout).await { Ok(res) => res?, Err(_) => return Err(PingError::DestinationHostUnreachable), } // Return the round trip duration Ok(now.elapsed()) } #[cfg(feature = "proto-ipv6")] async fn single_ping_v6(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result { let ping_repr = self.create_repr_ipv6(params, seq_no); // Create the socket and set hop limit and bind it to the endpoint with the ident let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer); socket.set_hop_limit(params.hop_limit); if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) { return Err(PingError::SocketBindError(e)); } // Helper func to fill the buffer when sending the ICMP packet fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv6Repr<'_>, params: &PingParams<'_>) -> Instant { let mut icmp_packet = Icmpv6Packet::new_unchecked(buf); let target = match params.target().unwrap() { IpAddr::V4(_) => unreachable!(), IpAddr::V6(addr) => addr, }; ping_repr.emit( ¶ms.source().unwrap(), &target, &mut icmp_packet, &ChecksumCapabilities::default(), ); Instant::now() } // Send with timeout the ICMP packet filling it with the helper function let send_result = socket .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| { (buf.len(), fill_packet_buffer(buf, ping_repr, params)) }) .with_timeout(Duration::from_millis(100)) .await; let now = match send_result { Ok(send_result) => match send_result { Ok(i) => i, Err(e) => return Err(PingError::SocketSendError(e)), }, Err(_) => return Err(PingError::SocketSendTimeout), }; // Helper function for the recieve helper function to validate the echo reply fn filter_pong(buf: &[u8], seq_no: u16) -> bool { let pong_packet = match Icmpv6Packet::new_checked(buf) { Ok(pak) => pak, Err(_) => return false, }; pong_packet.echo_seq_no() == seq_no } // Helper function to recieve and return the correct echo reply when it finds it async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await { Ok(b) => !b, Err(e) => return Err(PingError::SocketRecvError(e)), } {} Ok(()) } // Calls the recieve helper function with a timeout match recv_pong(&socket, seq_no).with_timeout(params.timeout).await { Ok(res) => res?, Err(_) => return Err(PingError::DestinationHostUnreachable), } // Return the round trip duration Ok(now.elapsed()) } } /// Parameters for configuring the ping operation. /// /// This struct provides various configuration options for performing ICMP ping operations, /// including the target IP address, payload data, hop limit, number of pings, and timeout duration. /// /// # Fields /// /// * `target` - The target IP address for the ping operation. /// * `source` - The source IP address for the ping operation (IPv6 only). /// * `payload` - The data to be sent in the payload field of the ping. /// * `hop_limit` - The hop limit to be used by the socket. /// * `count` - The number of pings to be sent in one ping operation. /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error. /// * `rate_limit` - The minimum time per echo request. pub struct PingParams<'a> { target: Option, #[cfg(feature = "proto-ipv6")] source: Option, payload: &'a [u8], hop_limit: Option, count: u16, timeout: Duration, rate_limit: Duration, } impl Default for PingParams<'_> { fn default() -> Self { Self { target: None, #[cfg(feature = "proto-ipv6")] source: None, payload: b"embassy-net", hop_limit: None, count: 4, timeout: Duration::from_secs(4), rate_limit: Duration::from_secs(1), } } } impl<'a> PingParams<'a> { /// Creates a new instance of [`PingParams`] with the specified target IP address. pub fn new>(target: T) -> Self { Self { target: Some(PingParams::ip_addr_to_smoltcp(target)), #[cfg(feature = "proto-ipv6")] source: None, payload: b"embassy-net", hop_limit: None, count: 4, timeout: Duration::from_secs(4), rate_limit: Duration::from_secs(1), } } fn ip_addr_to_smoltcp>(ip_addr: T) -> IpAddress { match ip_addr.into() { #[cfg(feature = "proto-ipv4")] IpAddr::V4(v4) => IpAddress::Ipv4(v4), #[cfg(not(feature = "proto-ipv4"))] IpAddr::V4(_) => unreachable!(), #[cfg(feature = "proto-ipv6")] IpAddr::V6(v6) => IpAddress::Ipv6(v6), #[cfg(not(feature = "proto-ipv6"))] IpAddr::V6(_) => unreachable!(), } } /// Sets the target IP address for the ping. pub fn set_target>(&mut self, target: T) -> &mut Self { self.target = Some(PingParams::ip_addr_to_smoltcp(target)); self } /// Retrieves the target IP address for the ping. pub fn target(&self) -> Option { self.target.map(|t| t.into()) } /// Sets the source IP address for the ping (IPv6 only). #[cfg(feature = "proto-ipv6")] pub fn set_source>(&mut self, source: T) -> &mut Self { self.source = Some(source.into()); self } /// Retrieves the source IP address for the ping (IPv6 only). #[cfg(feature = "proto-ipv6")] pub fn source(&self) -> Option { self.source } /// Sets the data used in the payload field of the ping with the provided slice. pub fn set_payload(&mut self, payload: &'a [u8]) -> &mut Self { self.payload = payload; self } /// Gives a reference to the slice of data that's going to be sent in the payload field /// of the ping. pub fn payload(&self) -> &'a [u8] { self.payload } /// Sets the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit). /// /// **Note**: A hop limit of [`Some(0)`](Some()) is equivalent to a hop limit of [`None`]. pub fn set_hop_limit(&mut self, hop_limit: Option) -> &mut Self { let mut hop_limit = hop_limit; if hop_limit.is_some_and(|x| x == 0) { hop_limit = None } self.hop_limit = hop_limit; self } /// Retrieves the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit). pub fn hop_limit(&self) -> Option { self.hop_limit } /// Sets the count used for specifying the number of pings done on one /// [`ping()`](PingManager::ping) call. /// /// **Note**: A count of 0 will be set as 1. pub fn set_count(&mut self, count: u16) -> &mut Self { let mut count = count; if count == 0 { count = 1; } self.count = count; self } /// Retrieve the count used for specifying the number of pings done on one /// [`ping()`](PingManager::ping) call. pub fn count(&self) -> u16 { self.count } /// Sets the timeout used before returning [`PingError::DestinationHostUnreachable`] /// when waiting for the Echo Reply icmp packet. pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self { self.timeout = timeout; self } /// Retrieve the timeout used before returning [`PingError::DestinationHostUnreachable`] /// when waiting for the Echo Reply icmp packet. pub fn timeout(&self) -> Duration { self.timeout } /// Sets the `rate_limit`: minimum time per echo request. pub fn set_rate_limit(&mut self, rate_limit: Duration) -> &mut Self { self.rate_limit = rate_limit; self } /// Retrieve the rate_limit. pub fn rate_limit(&self) -> Duration { self.rate_limit } } } ================================================ FILE: embassy-net/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![allow(unsafe_op_in_unsafe_fn)] #![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 = "icmp")] pub mod icmp; #[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::{Future, poll_fn}; 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[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, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[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, } } /// Slaac configuration with dynamic addressing. #[cfg(feature = "slaac")] pub const fn slaac() -> Self { Self { #[cfg(feature = "proto-ipv4")] ipv4: ConfigV4::None, ipv6: ConfigV6::Slaac, } } } /// Network stack IPv4 configuration. #[cfg(feature = "proto-ipv4")] #[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ConfigV6 { /// Do not configure IPv6. #[default] None, /// Use a static IPv6 address configuration. Static(StaticConfigV6), /// Use SLAAC for IPv6 address configuration. #[cfg(feature = "slaac")] Slaac, } /// 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 = "slaac")] slaac: bool, #[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; #[cfg(feature = "slaac")] { iface_cfg.slaac = matches!(config.ipv6, ConfigV6::Slaac); } #[allow(unused_mut)] let mut 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 = "slaac")] slaac: false, #[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, slaac 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 { #[cfg(feature = "slaac")] fn get_link_local_address(&self) -> IpCidr { let ll_prefix = Ipv6Cidr::new(Ipv6Cidr::LINK_LOCAL_PREFIX.address(), 64); Ipv6Cidr::from_link_prefix(&ll_prefix, self.hardware_address) .unwrap() .into() } #[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) { #[cfg(feature = "slaac")] { self.slaac = matches!(config, ConfigV6::Slaac); } self.static_v6 = match config { ConfigV6::None => None, ConfigV6::Static(c) => Some(c), #[cfg(feature = "slaac")] ConfigV6::Slaac => None, }; } 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; #[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; }); // Add the link local-address #[cfg(feature = "slaac")] { let ll_address = self.get_link_local_address(); self.iface.update_ip_addrs(|a| { let _ = a.push(ll_address); }) } // 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(); } #[allow(unused_mut)] let mut configure = false; #[cfg(feature = "dhcpv4")] { configure |= if let Some(dhcp_handle) = self.dhcp_socket { let socket = self.sockets.get_mut::(dhcp_handle); 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 } } else { false } } #[cfg(feature = "slaac")] if self.slaac && self.iface.slaac_updated_at() == timestamp { let ipv6_address = self.iface.ip_addrs().iter().find_map(|addr| match addr { IpCidr::Ipv6(ip6_address) if !Ipv6Cidr::LINK_LOCAL_PREFIX.contains_addr(&ip6_address.address()) => { Some(ip6_address) } _ => None, }); self.static_v6 = if let Some(address) = ipv6_address { let gateway = self .iface .routes() .get_default_ipv6_route() .map(|r| match r.via_router { IpAddress::Ipv6(gateway) => Some(gateway), #[cfg(feature = "proto-ipv4")] _ => None, }) .flatten(); let config = StaticConfigV6 { address: *address, gateway, dns_servers: Vec::new(), // RDNSS not (yet) supported by smoltcp. }; Some(config) } else { None }; configure = true; } 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!() } } ================================================ FILE: embassy-net/src/raw.rs ================================================ //! Raw sockets. use core::future::{Future, poll_fn}; use core::mem; use core::task::{Context, Poll}; 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: Option, ip_protocol: Option, 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 fn wait_recv_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_recv_ready(cx)) } /// 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 fn wait_send_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_send_ready(cx)) } /// 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 fn send<'s>(&'s self, buf: &'s [u8]) -> impl Future + 's { poll_fn(|cx| self.poll_send(buf, cx)) } /// 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 fn flush(&mut self) -> impl Future + '_ { poll_fn(|cx| { self.with_mut(|s, _| { if s.send_queue() == 0 { Poll::Ready(()) } else { s.register_send_waker(cx.waker()); Poll::Pending } }) }) } } 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 } ================================================ FILE: embassy-net/src/tcp.rs ================================================ //! 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::{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::Stack; use crate::time::duration_to_smoltcp; /// 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 fn wait_read_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.io.poll_read_ready(cx)) } /// 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 fn wait_write_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.io.poll_write_ready(cx)) } /// 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 fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { self.io.write(buf) } /// 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 fn flush(&mut self) -> impl Future> + '_ { self.io.flush() } /// 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 fn wait_read_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.io.poll_read_ready(cx)) } /// 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 fn read<'s>(&'s mut self, buf: &'s mut [u8]) -> impl Future> + 's { self.io.read(buf) } /// 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 fn wait_write_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.io.poll_write_ready(cx)) } /// 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 fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { self.io.write(buf) } /// 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 fn flush(&mut self) -> impl Future> + '_ { self.io.flush() } /// 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)) } /// Enable or disable Nagles's algorithm. /// /// By default, Nagle's algorithm is enabled. /// When enabled, Nagle’s Algorithm prevents sending segments smaller /// than MSS if there is data in flight (sent but not acknowledged). /// In other words, it ensures at most only one segment smaller than /// MSS is in flight at a time. /// It ensures better network utilization by preventing sending many /// very small packets, at the cost of increased latency in some /// situations, particularly when the remote peer has ACK delay enabled. pub fn set_nagle_enabled(&mut self, enabled: bool) { self.io.with_mut(|s, _| s.set_nagle_enabled(enabled)) } /// 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 } }) } fn read<'s>(&'s mut self, buf: &'s mut [u8]) -> impl Future> + 's { poll_fn(|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)), }) }) } 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 } }) } fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { poll_fn(|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)), }) }) } 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 } fn flush(&mut self) -> impl Future> + '_ { poll_fn(|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(())) } }) }) } 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 core::fmt::Display for ConnectError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("ConnectError") } } impl core::error::Error for ConnectError {} 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 core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::ConnectionReset => f.write_str("ConnectionReset"), } } } impl core::error::Error for Error {} 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); } } } ================================================ FILE: embassy-net/src/time.rs ================================================ #![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()) } ================================================ FILE: embassy-net/src/udp.rs ================================================ //! UDP sockets. use core::future::{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::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, /// There is not enough transmit buffer capacity to ever send this packet. PacketTooLarge, } /// Error returned by [`UdpSocket::recv_from`]. #[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 fn wait_recv_ready(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_recv_ready(cx)) } /// 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 fn recv_from<'s>( &'s self, buf: &'s mut [u8], ) -> impl Future> + 's { poll_fn(|cx| self.poll_recv_from(buf, cx)) } /// 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 a reference to the received bytes 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 fn wait_send_ready(&self) -> impl Future + '_ { poll_fn(|cx| self.poll_send_ready(cx)) } /// 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. /// /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)` /// /// 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. /// /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))` /// /// 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, { // Don't need to wake waker in `with_mut` if the buffer will never fit the udp tx_buffer. let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len()); if send_capacity_too_small { return Poll::Ready(Err(SendError::PacketTooLarge)); } 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 /// passing it to the closure. The closure returns the number of bytes /// written into the buffer. /// /// If the socket's send buffer is too small to fit `max_size`, this method will return `Err(SendError::PacketTooLarge)` /// /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` pub async fn send_to_with(&mut self, max_size: usize, remote_endpoint: T, f: F) -> Result where T: Into + Copy, F: FnOnce(&mut [u8]) -> (usize, R), { // Don't need to wake waker in `with_mut` if the buffer will never fit the udp tx_buffer. let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < max_size); if send_capacity_too_small { return Err(SendError::PacketTooLarge); } let mut f = Some(f); poll_fn(move |cx| { self.with_mut(|s, _| { let mut ret = None; match s.send_with(max_size, remote_endpoint, |buf| { let (size, r) = unwrap!(f.take())(buf); ret = Some(r); size }) { Ok(_n) => Poll::Ready(Ok(unwrap!(ret))), 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 fn flush(&mut self) -> impl Future + '_ { poll_fn(|cx| { self.with_mut(|s, _| { if s.send_queue() == 0 { Poll::Ready(()) } else { s.register_send_waker(cx.waker()); Poll::Pending } }) }) } /// 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 } ================================================ FILE: embassy-net-adin1110/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.4.0 - 2026-03-10 - Update embassy-net-driver-channel to 0.4.0 ## 0.3.1 - 2025-08-26 - First release with changelog. ================================================ FILE: embassy-net-adin1110/Cargo.toml ================================================ [package] name = "embassy-net-adin1110" version = "0.4.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 = "2024" repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-net-adin1110" [dependencies] heapless = "0.9" defmt = { version = "1.0.1", 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.4.0", path = "../embassy-net-driver-channel" } embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-futures = { version = "0.1.2", 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"] ================================================ FILE: embassy-net-adin1110/README.md ================================================ # 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 ``` ================================================ FILE: embassy-net-adin1110/src/crc32.rs ================================================ /// 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); } } ================================================ FILE: embassy-net-adin1110/src/crc8.rs ================================================ /// 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_8_SMBUS, Crc}; 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); } } ================================================ FILE: embassy-net-adin1110/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net-adin1110/src/lib.rs ================================================ #![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; use crc8::crc8; pub use crc32::ETH_FCS; use embassy_futures::select::{Either, select}; 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(); } } ================================================ FILE: embassy-net-adin1110/src/mdio.rs ================================================ /// 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) ] ); } } ================================================ FILE: embassy-net-adin1110/src/phy.rs ================================================ 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 } } ================================================ FILE: embassy-net-adin1110/src/regs.rs ================================================ 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; } } ================================================ FILE: embassy-net-driver/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 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 ================================================ FILE: embassy-net-driver/Cargo.toml ================================================ [package] name = "embassy-net-driver" version = "0.2.0" edition = "2024" 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 = "1.0.1", optional = true } [features] defmt = ["dep:defmt"] ================================================ FILE: embassy-net-driver/README.md ================================================ # 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. ================================================ FILE: embassy-net-driver/src/lib.rs ================================================ #![no_std] #![allow(unsafe_op_in_unsafe_fn)] #![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, } ================================================ FILE: embassy-net-driver-channel/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.4.0 - 2026-03-11 - Update embassy-sync to 0.8.0 ## 0.3.2 - 2025-08-26 ## 0.3.1 - 2025-07-16 - Update `embassy-sync` to v0.7.0 ## 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 ================================================ FILE: embassy-net-driver-channel/Cargo.toml ================================================ [package] name = "embassy-net-driver-channel" version = "0.4.0" edition = "2024" 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 = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } [features] defmt = ["dep:defmt"] log = ["dep:log"] ================================================ FILE: embassy-net-driver-channel/README.md ================================================ # 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. ================================================ FILE: embassy-net-driver-channel/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net-driver-channel/src/lib.rs ================================================ #![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::Mutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex; 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 } } ================================================ FILE: embassy-net-enc28j60/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.3.0 - 2026-03-10 - Update embassy-net-driver-channel to 0.4.0 ## 0.2.1 - 2025-08-26 ## 0.1.1 - 2025-08-16 - First release with changelog. ================================================ FILE: embassy-net-enc28j60/Cargo.toml ================================================ [package] name = "embassy-net-enc28j60" version = "0.3.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 = "2024" 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.5.1", path = "../embassy-time" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } [features] defmt = ["dep:defmt", "embassy-net-driver/defmt"] log = ["dep:log"] [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"] ================================================ FILE: embassy-net-enc28j60/README.md ================================================ # `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. ================================================ FILE: embassy-net-enc28j60/src/bank0.rs ================================================ #[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) } } ================================================ FILE: embassy-net-enc28j60/src/bank1.rs ================================================ #[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, }); ================================================ FILE: embassy-net-enc28j60/src/bank2.rs ================================================ #[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, }); ================================================ FILE: embassy-net-enc28j60/src/bank3.rs ================================================ #[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) } } ================================================ FILE: embassy-net-enc28j60/src/common.rs ================================================ #[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, }); ================================================ FILE: embassy-net-enc28j60/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net-enc28j60/src/header.rs ================================================ 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, }); ================================================ FILE: embassy-net-enc28j60/src/lib.rs ================================================ #![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 } } ================================================ FILE: embassy-net-enc28j60/src/macros.rs ================================================ 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 } )+ } } } ================================================ FILE: embassy-net-enc28j60/src/phy.rs ================================================ #[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, }); ================================================ FILE: embassy-net-enc28j60/src/traits.rs ================================================ 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; ================================================ FILE: embassy-net-esp-hosted/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.3.0 - 2026-03-10 - Add an `Interface` trait to allow using other interface transports. - Switch to `micropb` for protobuf. - Update protos to latest `esp-hosted-fg`. - Add support for OTA firmware updates. - Update embassy-net-driver-channel to 0.4.0 - Update embassy-sync to 0.8.0 ## 0.2.1 - 2025-08-26 - First release with changelog. ================================================ FILE: embassy-net-esp-hosted/Cargo.toml ================================================ [package] name = "embassy-net-esp-hosted" version = "0.3.0" edition = "2024" 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"] log = ["dep:log"] [dependencies] defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-sync = { version = "0.8.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.2", path = "../embassy-futures"} embassy-net-driver-channel = { version = "0.4.0", path = "../embassy-net-driver-channel"} embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } micropb = { version = "0.6.0", default-features = false, features = ["container-heapless-0-9", "encode", "decode"] } heapless = "0.9" [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"] ================================================ FILE: embassy-net-esp-hosted/README.md ================================================ # ESP-Hosted `embassy-net` integration [`embassy-net`](https://crates.io/crates/embassy-net) integration for Espressif SoCs running 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). ================================================ FILE: embassy-net-esp-hosted/src/control.rs ================================================ use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; use heapless::String; use micropb::{MessageDecode, MessageEncode, PbEncoder}; 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::Ctrl_WifiSecProt 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, msg_type: proto::CtrlMsgType::Req, payload: Some(proto::CtrlMsg_::Payload::$req_variant($req)), req_resp_type: 0, uid: 0, }; $self.ioctl(&mut msg).await?; #[allow(unused_mut)] let Some(proto::CtrlMsg_::Payload::$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 as u32)); } }; } 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::CtrlMsg_Req_GetAPConfig {}; ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp); let ssid = core::str::from_utf8(&resp.ssid).map_err(|_| Error::Internal)?; let ssid = String::try_from(ssid.trim_end_matches('\0')).map_err(|_| Error::Internal)?; let bssid_str = core::str::from_utf8(&resp.bssid).map_err(|_| Error::Internal)?; Ok(Status { ssid, bssid: parse_mac(bssid_str)?, rssi: resp.rssi as _, channel: resp.chnl as u32, 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> { const WIFI_BAND_MODE_AUTO: i32 = 3; // 2.4GHz + 5GHz let req = proto::CtrlMsg_Req_ConnectAP { ssid: unwrap!(String::try_from(ssid)), pwd: unwrap!(String::try_from(password)), bssid: String::new(), listen_interval: 3, is_wpa3_supported: true, band_mode: WIFI_BAND_MODE_AUTO, }; ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); // TODO: in newer esp-hosted firmwares that added EventStationConnectedToAp // the connect ioctl seems to be async, so we shouldn't immediately set LinkState::Up here. 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::CtrlMsg_Req_GetStatus {}; ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp); self.state_ch.set_link_state(LinkState::Down); Ok(()) } /// Initiate a firmware update. pub async fn ota_begin(&mut self) -> Result<(), Error> { let req = proto::CtrlMsg_Req_OTABegin {}; ioctl!(self, ReqOtaBegin, RespOtaBegin, req, resp); Ok(()) } /// Write slice of firmware to a device. /// /// [`ota_begin`] must be called first. /// /// The slice is split into chunks that can be sent across /// the ioctl protocol to the wifi adapter. pub async fn ota_write(&mut self, data: &[u8]) -> Result<(), Error> { for chunk in data.chunks(256) { let req = proto::CtrlMsg_Req_OTAWrite { ota_data: heapless::Vec::from_slice(chunk).unwrap(), }; ioctl!(self, ReqOtaWrite, RespOtaWrite, req, resp); } Ok(()) } /// End the OTA session. /// /// [`ota_begin`] must be called first. /// /// NOTE: Will reset the wifi adapter after 5 seconds. pub async fn ota_end(&mut self) -> Result<(), Error> { let req = proto::CtrlMsg_Req_OTAEnd {}; ioctl!(self, ReqOtaEnd, RespOtaEnd, req, resp); self.shared.ota_done(); // Wait for re-init self.init().await?; Ok(()) } /// duration in seconds, clamped to [10, 3600] async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { let req = proto::CtrlMsg_Req_ConfigHeartbeat { enable: true, duration: duration as i32, }; ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp); Ok(()) } async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> { let req = proto::CtrlMsg_Req_GetMacAddress { mode: WifiMode::Sta as _, }; ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp); let mac_str = core::str::from_utf8(&resp.mac).map_err(|_| Error::Internal)?; parse_mac(mac_str) } async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> { let req = proto::CtrlMsg_Req_SetMode { mode: mode as i32 }; ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp); Ok(()) } async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { debug!("ioctl req: {:?}", &msg); // Theoretical max overhead is 29 bytes. Biggest message is OTA write with 256 bytes. let mut buf = [0u8; 256 + 29]; let buf_len = buf.len(); let mut encoder = PbEncoder::new(&mut buf[..]); msg.encode(&mut encoder).map_err(|_| { warn!("failed to serialize control request"); Error::Internal })?; let remaining = encoder.into_writer(); let req_len = buf_len - remaining.len(); 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.decode_from_bytes(&buf[..resp_len]).map_err(|_| { warn!("failed to deserialize control response"); 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) } ================================================ FILE: embassy-net-esp-hosted/src/esp_hosted_config.proto ================================================ /* Copyright (C) 2015-2025 Espressif Systems (Shanghai) PTE LTD */ /* SPDX-License-Identifier: GPL-2.0-only OR Apache-2.0 */ 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 { NO_PS = 0; MIN_MODEM = 1; MAX_MODEM = 2; PS_Invalid = 3; } 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; Req_EnableDisable = 122; Req_GetFwVersion = 123; Req_SetCountryCode = 124; Req_GetCountryCode = 125; Req_SetDhcpDnsStatus = 126; Req_GetDhcpDnsStatus = 127; Req_Custom_RPC_Unserialised_Msg = 128; /* Add new control path command response before Req_Max * and update Req_Max */ Req_Max = 129; /** 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; Resp_EnableDisable = 222; Resp_GetFwVersion = 223; Resp_SetCountryCode = 224; Resp_GetCountryCode = 225; Resp_SetDhcpDnsStatus = 226; Resp_GetDhcpDnsStatus = 227; Resp_Custom_RPC_Unserialised_Msg = 228; /* Add new control path command response before Resp_Max * and update Resp_Max */ Resp_Max = 229; /** Event Msgs **/ Event_Base = 300; Event_ESPInit = 301; Event_Heartbeat = 302; Event_StationDisconnectFromAP = 303; Event_StationDisconnectFromESPSoftAP = 304; Event_StationConnectedToAP = 305; Event_StationConnectedToESPSoftAP = 306; Event_SetDhcpDnsStatus = 307; Event_Custom_RPC_Unserialised_Msg = 308; /* Add new control path command notification before Event_Max * and update Event_Max */ Event_Max = 309; } enum HostedFeature { Hosted_InvalidFeature = 0; Hosted_Wifi = 1; Hosted_Bluetooth = 2; Hosted_Is_Network_Split_On = 3; /* Add your new features here and re-build prot using build_proto.sh */ } /* 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; int32 band_mode = 7; } message CtrlMsg_Req_ConnectAP { string ssid = 1; string pwd = 2; string bssid = 3; bool is_wpa3_supported = 4; int32 listen_interval = 5; int32 band_mode = 6; } message CtrlMsg_Resp_ConnectAP { int32 resp = 1; bytes mac = 2; int32 band_mode = 3; } 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; int32 band_mode = 9; } 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; int32 band_mode = 8; } message CtrlMsg_Resp_StartSoftAP { int32 resp = 1; bytes mac = 2; int32 band_mode = 3; } 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; } message CtrlMsg_Req_EnableDisable { uint32 feature = 1; bool enable = 2; } message CtrlMsg_Resp_EnableDisable { int32 resp = 1; } message CtrlMsg_Req_GetFwVersion { } message CtrlMsg_Resp_GetFwVersion { int32 resp = 1; string name = 2; uint32 major1 = 3; uint32 major2 = 4; uint32 minor = 5; uint32 rev_patch1 = 6; uint32 rev_patch2 = 7; } message CtrlMsg_Req_SetCountryCode { bytes country = 1; bool ieee80211d_enabled = 2; } message CtrlMsg_Resp_SetCountryCode { int32 resp = 1; } message CtrlMsg_Req_GetCountryCode { } message CtrlMsg_Resp_GetCountryCode { int32 resp = 1; bytes country = 2; } message CtrlMsg_Req_SetDhcpDnsStatus { int32 iface = 1; int32 net_link_up = 2; int32 dhcp_up = 3; bytes dhcp_ip = 4; bytes dhcp_nm = 5; bytes dhcp_gw = 6; int32 dns_up = 7; bytes dns_ip = 8; int32 dns_type = 9; } message CtrlMsg_Resp_SetDhcpDnsStatus { int32 resp = 1; } message CtrlMsg_Req_GetDhcpDnsStatus { } message CtrlMsg_Resp_GetDhcpDnsStatus { int32 resp = 1; int32 iface = 2; int32 net_link_up = 3; int32 dhcp_up = 4; bytes dhcp_ip = 5; bytes dhcp_nm = 6; bytes dhcp_gw = 7; int32 dns_up = 8; bytes dns_ip = 9; int32 dns_type = 10; } /** 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; bytes ssid = 2; uint32 ssid_len = 3; bytes bssid = 4; uint32 reason = 5; int32 rssi = 6; } message CtrlMsg_Event_StationConnectedToAP { int32 resp = 1; bytes ssid = 2; uint32 ssid_len = 3; bytes bssid = 4; uint32 channel = 5; int32 authmode = 6; int32 aid = 7; } message CtrlMsg_Event_StationDisconnectFromESPSoftAP { int32 resp = 1; bytes mac = 2; uint32 aid = 3; bool is_mesh_child = 4; uint32 reason = 5; } message CtrlMsg_Event_StationConnectedToESPSoftAP { int32 resp = 1; bytes mac = 2; uint32 aid = 3; bool is_mesh_child = 4; } message CtrlMsg_Event_SetDhcpDnsStatus { int32 iface = 1; int32 net_link_up = 2; int32 dhcp_up = 3; bytes dhcp_ip = 4; bytes dhcp_nm = 5; bytes dhcp_gw = 6; int32 dns_up = 7; bytes dns_ip = 8; int32 dns_type = 9; int32 resp = 10; } /* Add Custom RPC message structures after existing message structures to make it easily notice */ message CtrlMsg_Req_CustomRpcUnserialisedMsg { uint32 custom_msg_id = 1; bytes data = 2; } message CtrlMsg_Resp_CustomRpcUnserialisedMsg { int32 resp = 1; uint32 custom_msg_id = 2; bytes data = 3; } message CtrlMsg_Event_CustomRpcUnserialisedMsg { int32 resp = 1; uint32 custom_evt_id = 2; bytes data = 3; } message CtrlMsg { /* msg_type could be req, resp or Event */ CtrlMsgType msg_type = 1; /* msg id */ CtrlMsgId msg_id = 2; /* UID of message */ int32 uid = 3; /* Request/response type: sync or async */ uint32 req_resp_type = 4; /* 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; CtrlMsg_Req_EnableDisable req_enable_disable_feat = 122; CtrlMsg_Req_GetFwVersion req_get_fw_version = 123; CtrlMsg_Req_SetCountryCode req_set_country_code = 124; CtrlMsg_Req_GetCountryCode req_get_country_code = 125; CtrlMsg_Req_SetDhcpDnsStatus req_set_dhcp_dns_status = 126; CtrlMsg_Req_GetDhcpDnsStatus req_get_dhcp_dns_status = 127; CtrlMsg_Req_CustomRpcUnserialisedMsg req_custom_rpc_unserialised_msg = 128; /** 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; CtrlMsg_Resp_EnableDisable resp_enable_disable_feat = 222; CtrlMsg_Resp_GetFwVersion resp_get_fw_version = 223; CtrlMsg_Resp_SetCountryCode resp_set_country_code = 224; CtrlMsg_Resp_GetCountryCode resp_get_country_code = 225; CtrlMsg_Resp_SetDhcpDnsStatus resp_set_dhcp_dns_status = 226; CtrlMsg_Resp_GetDhcpDnsStatus resp_get_dhcp_dns_status = 227; CtrlMsg_Resp_CustomRpcUnserialisedMsg resp_custom_rpc_unserialised_msg = 228; /** 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; CtrlMsg_Event_StationConnectedToAP event_station_connected_to_AP = 305; CtrlMsg_Event_StationConnectedToESPSoftAP event_station_connected_to_ESP_SoftAP = 306; CtrlMsg_Event_SetDhcpDnsStatus event_set_dhcp_dns_status = 307; CtrlMsg_Event_CustomRpcUnserialisedMsg event_custom_rpc_unserialised_msg = 308; } } ================================================ FILE: embassy-net-esp-hosted/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net-esp-hosted/src/iface.rs ================================================ use embassy_time::Timer; use embedded_hal::digital::InputPin; use embedded_hal_async::digital::Wait; use embedded_hal_async::spi::SpiDevice; /// Physical interface trait for communicating with the ESP chip. pub trait Interface { /// Wait for the HANDSHAKE signal indicating the ESP is ready for a new transaction. async fn wait_for_handshake(&mut self); /// Wait for the READY signal indicating the ESP has data to send. async fn wait_for_ready(&mut self); /// Perform a SPI transfer, exchanging data with the ESP chip. async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]); } /// Standard SPI interface. /// /// This interface is what's implemented in the upstream `esp-hosted-fg` firmware. It uses: /// - An `SpiDevice` for SPI communication (CS is handled by the device) /// - A handshake pin that signals when the ESP is ready for a new transaction /// - A ready pin that indicates when the ESP has data to send pub struct SpiInterface { spi: SPI, handshake: IN, ready: IN, } impl SpiInterface where SPI: SpiDevice, IN: InputPin + Wait, { /// Create a new SpiInterface. pub fn new(spi: SPI, handshake: IN, ready: IN) -> Self { Self { spi, handshake, ready } } } impl Interface for SpiInterface where SPI: SpiDevice, IN: InputPin + Wait, { async fn wait_for_handshake(&mut self) { self.handshake.wait_for_high().await.unwrap(); } async fn wait_for_ready(&mut self) { self.ready.wait_for_high().await.unwrap(); } async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) { self.spi.transfer(rx, tx).await.unwrap(); // The esp-hosted firmware deasserts the HANDSHAKE 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. Timer::after_micros(100).await; } } ================================================ FILE: embassy-net-esp-hosted/src/ioctl.rs ================================================ use core::cell::RefCell; use core::future::{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, state: ControlState, control_waker: WakerRegistration, runner_waker: WakerRegistration, } #[derive(Clone, Copy)] pub(crate) enum ControlState { Init, Reboot, Ready, } impl Shared { pub fn new() -> Self { Self(RefCell::new(SharedInner { ioctl: IoctlState::Done { resp_len: 0 }, state: ControlState::Init, control_waker: WakerRegistration::new(), runner_waker: WakerRegistration::new(), })) } pub fn ioctl_wait_complete(&self) -> impl Future + '_ { 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 } }) } 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"); } } // ota pub fn ota_done(&self) { let mut this = self.0.borrow_mut(); this.state = ControlState::Reboot; } // // // // // // // // // // // // // // // // // // // // // // check if ota is in progress pub(crate) fn state(&self) -> ControlState { let this = self.0.borrow(); this.state } pub fn init_done(&self) { let mut this = self.0.borrow_mut(); this.state = ControlState::Ready; this.control_waker.wake(); } pub fn init_wait(&self) -> impl Future + '_ { poll_fn(|cx| { let mut this = self.0.borrow_mut(); if let ControlState::Ready = this.state { Poll::Ready(()) } else { this.control_waker.register(cx.waker()); Poll::Pending } }) } } ================================================ FILE: embassy-net-esp-hosted/src/lib.rs ================================================ #![no_std] #![doc = include_str!("../README.md")] #![warn(missing_docs)] #![allow(async_fn_in_trait)] use embassy_futures::select::{Either4, select4}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; use embassy_time::{Duration, Instant, Timer}; use embedded_hal::digital::OutputPin; use crate::ioctl::{PendingIoctl, Shared}; use crate::proto::{CtrlMsg, CtrlMsg_}; #[allow(unused)] #[allow(non_snake_case)] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] #[allow(missing_docs)] #[allow(clippy::all)] mod proto; // must be first mod fmt; mod control; mod iface; mod ioctl; pub use control::*; pub use iface::*; 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, interface, and reset pin. /// /// 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, I, OUT>( state: &'a mut State, iface: I, reset: OUT, ) -> (NetDriver<'a>, Control<'a>, Runner<'a, I, OUT>) where I: Interface, 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, reset, iface, 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, I, OUT> { ch: ch::Runner<'a, MTU>, state_ch: ch::StateRunner<'a>, shared: &'a Shared, next_seq: u16, heartbeat_deadline: Instant, iface: I, reset: OUT, } impl<'a, I, OUT> Runner<'a, I, OUT> where I: Interface, 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.iface.wait_for_handshake().await; let ioctl = self.shared.ioctl_wait_pending(); let tx = self.ch.tx_buf(); let ev = async { self.iface.wait_for_ready().await }; 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(()) => { // Extend the deadline if initializing if let ioctl::ControlState::Reboot = self.shared.state() { self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP; continue; } panic!("heartbeat from esp32 stopped") } } if tx_buf[0] != 0 { trace!("tx: {:02x}", &tx_buf[..40]); } self.iface.transfer(&mut rx_buf, &tx_buf).await; self.handle_rx(&mut rx_buf); } } 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]) { use micropb::MessageDecode; let mut event = CtrlMsg::default(); if event.decode_from_bytes(data).is_err() { warn!("failed to parse event"); return; } debug!("event: {:?}", &event); let Some(payload) = &event.payload else { warn!("event without payload?"); return; }; match payload { CtrlMsg_::Payload::EventEspInit(_) => self.shared.init_done(), CtrlMsg_::Payload::EventHeartbeat(_) => self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP, CtrlMsg_::Payload::EventStationConnectedToAp(e) => { info!("connected, code {}", e.resp); self.state_ch.set_link_state(LinkState::Up); } CtrlMsg_::Payload::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 } ================================================ FILE: embassy-net-esp-hosted/src/proto.rs ================================================ /// internal supporting structures for CtrlMsg #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ScanResult { pub r#ssid: ::heapless::Vec, pub r#chnl: u32, pub r#rssi: i32, pub r#bssid: ::heapless::Vec, pub r#sec_prot: Ctrl_WifiSecProt, } impl ScanResult { /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::Vec { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::Vec) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `chnl` #[inline] pub fn r#chnl(&self) -> &u32 { &self.r#chnl } /// Return a mutable reference to `chnl` #[inline] pub fn mut_chnl(&mut self) -> &mut u32 { &mut self.r#chnl } /// Set the value of `chnl` #[inline] pub fn set_chnl(&mut self, value: u32) -> &mut Self { self.r#chnl = value.into(); self } /// Builder method that sets the value of `chnl`. Useful for initializing the message. #[inline] pub fn init_chnl(mut self, value: u32) -> Self { self.r#chnl = value.into(); self } /// Return a reference to `rssi` #[inline] pub fn r#rssi(&self) -> &i32 { &self.r#rssi } /// Return a mutable reference to `rssi` #[inline] pub fn mut_rssi(&mut self) -> &mut i32 { &mut self.r#rssi } /// Set the value of `rssi` #[inline] pub fn set_rssi(&mut self, value: i32) -> &mut Self { self.r#rssi = value.into(); self } /// Builder method that sets the value of `rssi`. Useful for initializing the message. #[inline] pub fn init_rssi(mut self, value: i32) -> Self { self.r#rssi = value.into(); self } /// Return a reference to `bssid` #[inline] pub fn r#bssid(&self) -> &::heapless::Vec { &self.r#bssid } /// Return a mutable reference to `bssid` #[inline] pub fn mut_bssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#bssid } /// Set the value of `bssid` #[inline] pub fn set_bssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#bssid = value.into(); self } /// Builder method that sets the value of `bssid`. Useful for initializing the message. #[inline] pub fn init_bssid(mut self, value: ::heapless::Vec) -> Self { self.r#bssid = value.into(); self } /// Return a reference to `sec_prot` #[inline] pub fn r#sec_prot(&self) -> &Ctrl_WifiSecProt { &self.r#sec_prot } /// Return a mutable reference to `sec_prot` #[inline] pub fn mut_sec_prot(&mut self) -> &mut Ctrl_WifiSecProt { &mut self.r#sec_prot } /// Set the value of `sec_prot` #[inline] pub fn set_sec_prot(&mut self, value: Ctrl_WifiSecProt) -> &mut Self { self.r#sec_prot = value.into(); self } /// Builder method that sets the value of `sec_prot`. Useful for initializing the message. #[inline] pub fn init_sec_prot(mut self, value: Ctrl_WifiSecProt) -> Self { self.r#sec_prot = value.into(); self } } impl ::micropb::MessageDecode for ScanResult { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#chnl; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#rssi; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#bssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 5u32 => { let mut_ref = &mut self.r#sec_prot; { let val = decoder.decode_int32().map(|n| Ctrl_WifiSecProt(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for ScanResult { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(Ctrl_WifiSecProt::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#chnl; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#rssi; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { encoder.encode_varint32(34u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { encoder.encode_varint32(40u32)?; encoder.encode_int32(val_ref.0 as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#chnl; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#rssi; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ConnectedSTAList { pub r#mac: ::heapless::Vec, pub r#rssi: i32, } impl ConnectedSTAList { /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `rssi` #[inline] pub fn r#rssi(&self) -> &i32 { &self.r#rssi } /// Return a mutable reference to `rssi` #[inline] pub fn mut_rssi(&mut self) -> &mut i32 { &mut self.r#rssi } /// Set the value of `rssi` #[inline] pub fn set_rssi(&mut self, value: i32) -> &mut Self { self.r#rssi = value.into(); self } /// Builder method that sets the value of `rssi`. Useful for initializing the message. #[inline] pub fn init_rssi(mut self, value: i32) -> Self { self.r#rssi = value.into(); self } } impl ::micropb::MessageDecode for ConnectedSTAList { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#rssi; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for ConnectedSTAList { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#rssi; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#rssi; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } ///* Req/Resp structure * #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetMacAddress { pub r#mode: i32, } impl CtrlMsg_Req_GetMacAddress { /// Return a reference to `mode` #[inline] pub fn r#mode(&self) -> &i32 { &self.r#mode } /// Return a mutable reference to `mode` #[inline] pub fn mut_mode(&mut self) -> &mut i32 { &mut self.r#mode } /// Set the value of `mode` #[inline] pub fn set_mode(&mut self, value: i32) -> &mut Self { self.r#mode = value.into(); self } /// Builder method that sets the value of `mode`. Useful for initializing the message. #[inline] pub fn init_mode(mut self, value: i32) -> Self { self.r#mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_GetMacAddress { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetMacAddress { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#mode; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetMacAddress { pub r#mac: ::heapless::Vec, pub r#resp: i32, } impl CtrlMsg_Resp_GetMacAddress { /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetMacAddress { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetMacAddress { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetMode {} impl CtrlMsg_Req_GetMode {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetMode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetMode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetMode { pub r#mode: i32, pub r#resp: i32, } impl CtrlMsg_Resp_GetMode { /// Return a reference to `mode` #[inline] pub fn r#mode(&self) -> &i32 { &self.r#mode } /// Return a mutable reference to `mode` #[inline] pub fn mut_mode(&mut self) -> &mut i32 { &mut self.r#mode } /// Set the value of `mode` #[inline] pub fn set_mode(&mut self, value: i32) -> &mut Self { self.r#mode = value.into(); self } /// Builder method that sets the value of `mode`. Useful for initializing the message. #[inline] pub fn init_mode(mut self, value: i32) -> Self { self.r#mode = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetMode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetMode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#mode; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SetMode { pub r#mode: i32, } impl CtrlMsg_Req_SetMode { /// Return a reference to `mode` #[inline] pub fn r#mode(&self) -> &i32 { &self.r#mode } /// Return a mutable reference to `mode` #[inline] pub fn mut_mode(&mut self) -> &mut i32 { &mut self.r#mode } /// Set the value of `mode` #[inline] pub fn set_mode(&mut self, value: i32) -> &mut Self { self.r#mode = value.into(); self } /// Builder method that sets the value of `mode`. Useful for initializing the message. #[inline] pub fn init_mode(mut self, value: i32) -> Self { self.r#mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_SetMode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SetMode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#mode; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SetMode { pub r#resp: i32, } impl CtrlMsg_Resp_SetMode { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SetMode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SetMode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetStatus {} impl CtrlMsg_Req_GetStatus {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetStatus { pub r#resp: i32, } impl CtrlMsg_Resp_GetStatus { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SetMacAddress { pub r#mac: ::heapless::Vec, pub r#mode: i32, } impl CtrlMsg_Req_SetMacAddress { /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `mode` #[inline] pub fn r#mode(&self) -> &i32 { &self.r#mode } /// Return a mutable reference to `mode` #[inline] pub fn mut_mode(&mut self) -> &mut i32 { &mut self.r#mode } /// Set the value of `mode` #[inline] pub fn set_mode(&mut self, value: i32) -> &mut Self { self.r#mode = value.into(); self } /// Builder method that sets the value of `mode`. Useful for initializing the message. #[inline] pub fn init_mode(mut self, value: i32) -> Self { self.r#mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_SetMacAddress { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SetMacAddress { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#mode; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SetMacAddress { pub r#resp: i32, } impl CtrlMsg_Resp_SetMacAddress { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SetMacAddress { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SetMacAddress { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetAPConfig {} impl CtrlMsg_Req_GetAPConfig {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetAPConfig { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetAPConfig { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetAPConfig { pub r#ssid: ::heapless::Vec, pub r#bssid: ::heapless::Vec, pub r#rssi: i32, pub r#chnl: i32, pub r#sec_prot: Ctrl_WifiSecProt, pub r#resp: i32, pub r#band_mode: i32, } impl CtrlMsg_Resp_GetAPConfig { /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::Vec { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::Vec) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `bssid` #[inline] pub fn r#bssid(&self) -> &::heapless::Vec { &self.r#bssid } /// Return a mutable reference to `bssid` #[inline] pub fn mut_bssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#bssid } /// Set the value of `bssid` #[inline] pub fn set_bssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#bssid = value.into(); self } /// Builder method that sets the value of `bssid`. Useful for initializing the message. #[inline] pub fn init_bssid(mut self, value: ::heapless::Vec) -> Self { self.r#bssid = value.into(); self } /// Return a reference to `rssi` #[inline] pub fn r#rssi(&self) -> &i32 { &self.r#rssi } /// Return a mutable reference to `rssi` #[inline] pub fn mut_rssi(&mut self) -> &mut i32 { &mut self.r#rssi } /// Set the value of `rssi` #[inline] pub fn set_rssi(&mut self, value: i32) -> &mut Self { self.r#rssi = value.into(); self } /// Builder method that sets the value of `rssi`. Useful for initializing the message. #[inline] pub fn init_rssi(mut self, value: i32) -> Self { self.r#rssi = value.into(); self } /// Return a reference to `chnl` #[inline] pub fn r#chnl(&self) -> &i32 { &self.r#chnl } /// Return a mutable reference to `chnl` #[inline] pub fn mut_chnl(&mut self) -> &mut i32 { &mut self.r#chnl } /// Set the value of `chnl` #[inline] pub fn set_chnl(&mut self, value: i32) -> &mut Self { self.r#chnl = value.into(); self } /// Builder method that sets the value of `chnl`. Useful for initializing the message. #[inline] pub fn init_chnl(mut self, value: i32) -> Self { self.r#chnl = value.into(); self } /// Return a reference to `sec_prot` #[inline] pub fn r#sec_prot(&self) -> &Ctrl_WifiSecProt { &self.r#sec_prot } /// Return a mutable reference to `sec_prot` #[inline] pub fn mut_sec_prot(&mut self) -> &mut Ctrl_WifiSecProt { &mut self.r#sec_prot } /// Set the value of `sec_prot` #[inline] pub fn set_sec_prot(&mut self, value: Ctrl_WifiSecProt) -> &mut Self { self.r#sec_prot = value.into(); self } /// Builder method that sets the value of `sec_prot`. Useful for initializing the message. #[inline] pub fn init_sec_prot(mut self, value: Ctrl_WifiSecProt) -> Self { self.r#sec_prot = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `band_mode` #[inline] pub fn r#band_mode(&self) -> &i32 { &self.r#band_mode } /// Return a mutable reference to `band_mode` #[inline] pub fn mut_band_mode(&mut self) -> &mut i32 { &mut self.r#band_mode } /// Set the value of `band_mode` #[inline] pub fn set_band_mode(&mut self, value: i32) -> &mut Self { self.r#band_mode = value.into(); self } /// Builder method that sets the value of `band_mode`. Useful for initializing the message. #[inline] pub fn init_band_mode(mut self, value: i32) -> Self { self.r#band_mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetAPConfig { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#bssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#rssi; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#chnl; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#sec_prot; { let val = decoder.decode_int32().map(|n| Ctrl_WifiSecProt(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 7u32 => { let mut_ref = &mut self.r#band_mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetAPConfig { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(Ctrl_WifiSecProt::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#rssi; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#chnl; if *val_ref != 0 { encoder.encode_varint32(32u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { encoder.encode_varint32(40u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(48u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#rssi; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#chnl; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_ConnectAP { pub r#ssid: ::heapless::String<32>, pub r#pwd: ::heapless::String<32>, pub r#bssid: ::heapless::String<32>, pub r#is_wpa3_supported: bool, pub r#listen_interval: i32, pub r#band_mode: i32, } impl CtrlMsg_Req_ConnectAP { /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::String<32> { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::String<32> { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::String<32>) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::String<32>) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `pwd` #[inline] pub fn r#pwd(&self) -> &::heapless::String<32> { &self.r#pwd } /// Return a mutable reference to `pwd` #[inline] pub fn mut_pwd(&mut self) -> &mut ::heapless::String<32> { &mut self.r#pwd } /// Set the value of `pwd` #[inline] pub fn set_pwd(&mut self, value: ::heapless::String<32>) -> &mut Self { self.r#pwd = value.into(); self } /// Builder method that sets the value of `pwd`. Useful for initializing the message. #[inline] pub fn init_pwd(mut self, value: ::heapless::String<32>) -> Self { self.r#pwd = value.into(); self } /// Return a reference to `bssid` #[inline] pub fn r#bssid(&self) -> &::heapless::String<32> { &self.r#bssid } /// Return a mutable reference to `bssid` #[inline] pub fn mut_bssid(&mut self) -> &mut ::heapless::String<32> { &mut self.r#bssid } /// Set the value of `bssid` #[inline] pub fn set_bssid(&mut self, value: ::heapless::String<32>) -> &mut Self { self.r#bssid = value.into(); self } /// Builder method that sets the value of `bssid`. Useful for initializing the message. #[inline] pub fn init_bssid(mut self, value: ::heapless::String<32>) -> Self { self.r#bssid = value.into(); self } /// Return a reference to `is_wpa3_supported` #[inline] pub fn r#is_wpa3_supported(&self) -> &bool { &self.r#is_wpa3_supported } /// Return a mutable reference to `is_wpa3_supported` #[inline] pub fn mut_is_wpa3_supported(&mut self) -> &mut bool { &mut self.r#is_wpa3_supported } /// Set the value of `is_wpa3_supported` #[inline] pub fn set_is_wpa3_supported(&mut self, value: bool) -> &mut Self { self.r#is_wpa3_supported = value.into(); self } /// Builder method that sets the value of `is_wpa3_supported`. Useful for initializing the message. #[inline] pub fn init_is_wpa3_supported(mut self, value: bool) -> Self { self.r#is_wpa3_supported = value.into(); self } /// Return a reference to `listen_interval` #[inline] pub fn r#listen_interval(&self) -> &i32 { &self.r#listen_interval } /// Return a mutable reference to `listen_interval` #[inline] pub fn mut_listen_interval(&mut self) -> &mut i32 { &mut self.r#listen_interval } /// Set the value of `listen_interval` #[inline] pub fn set_listen_interval(&mut self, value: i32) -> &mut Self { self.r#listen_interval = value.into(); self } /// Builder method that sets the value of `listen_interval`. Useful for initializing the message. #[inline] pub fn init_listen_interval(mut self, value: i32) -> Self { self.r#listen_interval = value.into(); self } /// Return a reference to `band_mode` #[inline] pub fn r#band_mode(&self) -> &i32 { &self.r#band_mode } /// Return a mutable reference to `band_mode` #[inline] pub fn mut_band_mode(&mut self) -> &mut i32 { &mut self.r#band_mode } /// Set the value of `band_mode` #[inline] pub fn set_band_mode(&mut self, value: i32) -> &mut Self { self.r#band_mode = value.into(); self } /// Builder method that sets the value of `band_mode`. Useful for initializing the message. #[inline] pub fn init_band_mode(mut self, value: i32) -> Self { self.r#band_mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_ConnectAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_string(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#pwd; { decoder.decode_string(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#bssid; { decoder.decode_string(mut_ref, ::micropb::Presence::Implicit)?; }; } 4u32 => { let mut_ref = &mut self.r#is_wpa3_supported; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#listen_interval; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#band_mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_ConnectAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_string(val_ref)?; } } { let val_ref = &self.r#pwd; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_string(val_ref)?; } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { encoder.encode_varint32(26u32)?; encoder.encode_string(val_ref)?; } } { let val_ref = &self.r#is_wpa3_supported; if *val_ref { encoder.encode_varint32(32u32)?; encoder.encode_bool(*val_ref)?; } } { let val_ref = &self.r#listen_interval; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { encoder.encode_varint32(48u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#pwd; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#is_wpa3_supported; if *val_ref { size += 1usize + 1; } } { let val_ref = &self.r#listen_interval; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_ConnectAP { pub r#resp: i32, pub r#mac: ::heapless::Vec, pub r#band_mode: i32, } impl CtrlMsg_Resp_ConnectAP { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `band_mode` #[inline] pub fn r#band_mode(&self) -> &i32 { &self.r#band_mode } /// Return a mutable reference to `band_mode` #[inline] pub fn mut_band_mode(&mut self) -> &mut i32 { &mut self.r#band_mode } /// Set the value of `band_mode` #[inline] pub fn set_band_mode(&mut self, value: i32) -> &mut Self { self.r#band_mode = value.into(); self } /// Builder method that sets the value of `band_mode`. Useful for initializing the message. #[inline] pub fn init_band_mode(mut self, value: i32) -> Self { self.r#band_mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_ConnectAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#band_mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_ConnectAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetSoftAPConfig {} impl CtrlMsg_Req_GetSoftAPConfig {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetSoftAPConfig { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetSoftAPConfig { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetSoftAPConfig { pub r#ssid: ::heapless::Vec, pub r#pwd: ::heapless::Vec, pub r#chnl: i32, pub r#sec_prot: Ctrl_WifiSecProt, pub r#max_conn: i32, pub r#ssid_hidden: bool, pub r#bw: i32, pub r#resp: i32, pub r#band_mode: i32, } impl CtrlMsg_Resp_GetSoftAPConfig { /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::Vec { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::Vec) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `pwd` #[inline] pub fn r#pwd(&self) -> &::heapless::Vec { &self.r#pwd } /// Return a mutable reference to `pwd` #[inline] pub fn mut_pwd(&mut self) -> &mut ::heapless::Vec { &mut self.r#pwd } /// Set the value of `pwd` #[inline] pub fn set_pwd(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#pwd = value.into(); self } /// Builder method that sets the value of `pwd`. Useful for initializing the message. #[inline] pub fn init_pwd(mut self, value: ::heapless::Vec) -> Self { self.r#pwd = value.into(); self } /// Return a reference to `chnl` #[inline] pub fn r#chnl(&self) -> &i32 { &self.r#chnl } /// Return a mutable reference to `chnl` #[inline] pub fn mut_chnl(&mut self) -> &mut i32 { &mut self.r#chnl } /// Set the value of `chnl` #[inline] pub fn set_chnl(&mut self, value: i32) -> &mut Self { self.r#chnl = value.into(); self } /// Builder method that sets the value of `chnl`. Useful for initializing the message. #[inline] pub fn init_chnl(mut self, value: i32) -> Self { self.r#chnl = value.into(); self } /// Return a reference to `sec_prot` #[inline] pub fn r#sec_prot(&self) -> &Ctrl_WifiSecProt { &self.r#sec_prot } /// Return a mutable reference to `sec_prot` #[inline] pub fn mut_sec_prot(&mut self) -> &mut Ctrl_WifiSecProt { &mut self.r#sec_prot } /// Set the value of `sec_prot` #[inline] pub fn set_sec_prot(&mut self, value: Ctrl_WifiSecProt) -> &mut Self { self.r#sec_prot = value.into(); self } /// Builder method that sets the value of `sec_prot`. Useful for initializing the message. #[inline] pub fn init_sec_prot(mut self, value: Ctrl_WifiSecProt) -> Self { self.r#sec_prot = value.into(); self } /// Return a reference to `max_conn` #[inline] pub fn r#max_conn(&self) -> &i32 { &self.r#max_conn } /// Return a mutable reference to `max_conn` #[inline] pub fn mut_max_conn(&mut self) -> &mut i32 { &mut self.r#max_conn } /// Set the value of `max_conn` #[inline] pub fn set_max_conn(&mut self, value: i32) -> &mut Self { self.r#max_conn = value.into(); self } /// Builder method that sets the value of `max_conn`. Useful for initializing the message. #[inline] pub fn init_max_conn(mut self, value: i32) -> Self { self.r#max_conn = value.into(); self } /// Return a reference to `ssid_hidden` #[inline] pub fn r#ssid_hidden(&self) -> &bool { &self.r#ssid_hidden } /// Return a mutable reference to `ssid_hidden` #[inline] pub fn mut_ssid_hidden(&mut self) -> &mut bool { &mut self.r#ssid_hidden } /// Set the value of `ssid_hidden` #[inline] pub fn set_ssid_hidden(&mut self, value: bool) -> &mut Self { self.r#ssid_hidden = value.into(); self } /// Builder method that sets the value of `ssid_hidden`. Useful for initializing the message. #[inline] pub fn init_ssid_hidden(mut self, value: bool) -> Self { self.r#ssid_hidden = value.into(); self } /// Return a reference to `bw` #[inline] pub fn r#bw(&self) -> &i32 { &self.r#bw } /// Return a mutable reference to `bw` #[inline] pub fn mut_bw(&mut self) -> &mut i32 { &mut self.r#bw } /// Set the value of `bw` #[inline] pub fn set_bw(&mut self, value: i32) -> &mut Self { self.r#bw = value.into(); self } /// Builder method that sets the value of `bw`. Useful for initializing the message. #[inline] pub fn init_bw(mut self, value: i32) -> Self { self.r#bw = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `band_mode` #[inline] pub fn r#band_mode(&self) -> &i32 { &self.r#band_mode } /// Return a mutable reference to `band_mode` #[inline] pub fn mut_band_mode(&mut self) -> &mut i32 { &mut self.r#band_mode } /// Set the value of `band_mode` #[inline] pub fn set_band_mode(&mut self, value: i32) -> &mut Self { self.r#band_mode = value.into(); self } /// Builder method that sets the value of `band_mode`. Useful for initializing the message. #[inline] pub fn init_band_mode(mut self, value: i32) -> Self { self.r#band_mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetSoftAPConfig { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#pwd; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#chnl; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#sec_prot; { let val = decoder.decode_int32().map(|n| Ctrl_WifiSecProt(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#max_conn; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#ssid_hidden; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } 7u32 => { let mut_ref = &mut self.r#bw; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 8u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 9u32 => { let mut_ref = &mut self.r#band_mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetSoftAPConfig { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(Ctrl_WifiSecProt::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#pwd; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#chnl; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { encoder.encode_varint32(32u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { let val_ref = &self.r#max_conn; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#ssid_hidden; if *val_ref { encoder.encode_varint32(48u32)?; encoder.encode_bool(*val_ref)?; } } { let val_ref = &self.r#bw; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(64u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { encoder.encode_varint32(72u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#pwd; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#chnl; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { let val_ref = &self.r#max_conn; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#ssid_hidden; if *val_ref { size += 1usize + 1; } } { let val_ref = &self.r#bw; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_StartSoftAP { pub r#ssid: ::heapless::String<32>, pub r#pwd: ::heapless::String<32>, pub r#chnl: i32, pub r#sec_prot: Ctrl_WifiSecProt, pub r#max_conn: i32, pub r#ssid_hidden: bool, pub r#bw: i32, pub r#band_mode: i32, } impl CtrlMsg_Req_StartSoftAP { /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::String<32> { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::String<32> { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::String<32>) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::String<32>) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `pwd` #[inline] pub fn r#pwd(&self) -> &::heapless::String<32> { &self.r#pwd } /// Return a mutable reference to `pwd` #[inline] pub fn mut_pwd(&mut self) -> &mut ::heapless::String<32> { &mut self.r#pwd } /// Set the value of `pwd` #[inline] pub fn set_pwd(&mut self, value: ::heapless::String<32>) -> &mut Self { self.r#pwd = value.into(); self } /// Builder method that sets the value of `pwd`. Useful for initializing the message. #[inline] pub fn init_pwd(mut self, value: ::heapless::String<32>) -> Self { self.r#pwd = value.into(); self } /// Return a reference to `chnl` #[inline] pub fn r#chnl(&self) -> &i32 { &self.r#chnl } /// Return a mutable reference to `chnl` #[inline] pub fn mut_chnl(&mut self) -> &mut i32 { &mut self.r#chnl } /// Set the value of `chnl` #[inline] pub fn set_chnl(&mut self, value: i32) -> &mut Self { self.r#chnl = value.into(); self } /// Builder method that sets the value of `chnl`. Useful for initializing the message. #[inline] pub fn init_chnl(mut self, value: i32) -> Self { self.r#chnl = value.into(); self } /// Return a reference to `sec_prot` #[inline] pub fn r#sec_prot(&self) -> &Ctrl_WifiSecProt { &self.r#sec_prot } /// Return a mutable reference to `sec_prot` #[inline] pub fn mut_sec_prot(&mut self) -> &mut Ctrl_WifiSecProt { &mut self.r#sec_prot } /// Set the value of `sec_prot` #[inline] pub fn set_sec_prot(&mut self, value: Ctrl_WifiSecProt) -> &mut Self { self.r#sec_prot = value.into(); self } /// Builder method that sets the value of `sec_prot`. Useful for initializing the message. #[inline] pub fn init_sec_prot(mut self, value: Ctrl_WifiSecProt) -> Self { self.r#sec_prot = value.into(); self } /// Return a reference to `max_conn` #[inline] pub fn r#max_conn(&self) -> &i32 { &self.r#max_conn } /// Return a mutable reference to `max_conn` #[inline] pub fn mut_max_conn(&mut self) -> &mut i32 { &mut self.r#max_conn } /// Set the value of `max_conn` #[inline] pub fn set_max_conn(&mut self, value: i32) -> &mut Self { self.r#max_conn = value.into(); self } /// Builder method that sets the value of `max_conn`. Useful for initializing the message. #[inline] pub fn init_max_conn(mut self, value: i32) -> Self { self.r#max_conn = value.into(); self } /// Return a reference to `ssid_hidden` #[inline] pub fn r#ssid_hidden(&self) -> &bool { &self.r#ssid_hidden } /// Return a mutable reference to `ssid_hidden` #[inline] pub fn mut_ssid_hidden(&mut self) -> &mut bool { &mut self.r#ssid_hidden } /// Set the value of `ssid_hidden` #[inline] pub fn set_ssid_hidden(&mut self, value: bool) -> &mut Self { self.r#ssid_hidden = value.into(); self } /// Builder method that sets the value of `ssid_hidden`. Useful for initializing the message. #[inline] pub fn init_ssid_hidden(mut self, value: bool) -> Self { self.r#ssid_hidden = value.into(); self } /// Return a reference to `bw` #[inline] pub fn r#bw(&self) -> &i32 { &self.r#bw } /// Return a mutable reference to `bw` #[inline] pub fn mut_bw(&mut self) -> &mut i32 { &mut self.r#bw } /// Set the value of `bw` #[inline] pub fn set_bw(&mut self, value: i32) -> &mut Self { self.r#bw = value.into(); self } /// Builder method that sets the value of `bw`. Useful for initializing the message. #[inline] pub fn init_bw(mut self, value: i32) -> Self { self.r#bw = value.into(); self } /// Return a reference to `band_mode` #[inline] pub fn r#band_mode(&self) -> &i32 { &self.r#band_mode } /// Return a mutable reference to `band_mode` #[inline] pub fn mut_band_mode(&mut self) -> &mut i32 { &mut self.r#band_mode } /// Set the value of `band_mode` #[inline] pub fn set_band_mode(&mut self, value: i32) -> &mut Self { self.r#band_mode = value.into(); self } /// Builder method that sets the value of `band_mode`. Useful for initializing the message. #[inline] pub fn init_band_mode(mut self, value: i32) -> Self { self.r#band_mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_StartSoftAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_string(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#pwd; { decoder.decode_string(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#chnl; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#sec_prot; { let val = decoder.decode_int32().map(|n| Ctrl_WifiSecProt(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#max_conn; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#ssid_hidden; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } 7u32 => { let mut_ref = &mut self.r#bw; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 8u32 => { let mut_ref = &mut self.r#band_mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_StartSoftAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(Ctrl_WifiSecProt::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_string(val_ref)?; } } { let val_ref = &self.r#pwd; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_string(val_ref)?; } } { let val_ref = &self.r#chnl; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { encoder.encode_varint32(32u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { let val_ref = &self.r#max_conn; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#ssid_hidden; if *val_ref { encoder.encode_varint32(48u32)?; encoder.encode_bool(*val_ref)?; } } { let val_ref = &self.r#bw; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { encoder.encode_varint32(64u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#pwd; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#chnl; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#sec_prot; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { let val_ref = &self.r#max_conn; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#ssid_hidden; if *val_ref { size += 1usize + 1; } } { let val_ref = &self.r#bw; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_StartSoftAP { pub r#resp: i32, pub r#mac: ::heapless::Vec, pub r#band_mode: i32, } impl CtrlMsg_Resp_StartSoftAP { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `band_mode` #[inline] pub fn r#band_mode(&self) -> &i32 { &self.r#band_mode } /// Return a mutable reference to `band_mode` #[inline] pub fn mut_band_mode(&mut self) -> &mut i32 { &mut self.r#band_mode } /// Set the value of `band_mode` #[inline] pub fn set_band_mode(&mut self, value: i32) -> &mut Self { self.r#band_mode = value.into(); self } /// Builder method that sets the value of `band_mode`. Useful for initializing the message. #[inline] pub fn init_band_mode(mut self, value: i32) -> Self { self.r#band_mode = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_StartSoftAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#band_mode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_StartSoftAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#band_mode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_ScanResult {} impl CtrlMsg_Req_ScanResult {} impl ::micropb::MessageDecode for CtrlMsg_Req_ScanResult { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_ScanResult { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_ScanResult { pub r#count: u32, pub r#entries: ::heapless::Vec, pub r#resp: i32, } impl CtrlMsg_Resp_ScanResult { /// Return a reference to `count` #[inline] pub fn r#count(&self) -> &u32 { &self.r#count } /// Return a mutable reference to `count` #[inline] pub fn mut_count(&mut self) -> &mut u32 { &mut self.r#count } /// Set the value of `count` #[inline] pub fn set_count(&mut self, value: u32) -> &mut Self { self.r#count = value.into(); self } /// Builder method that sets the value of `count`. Useful for initializing the message. #[inline] pub fn init_count(mut self, value: u32) -> Self { self.r#count = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_ScanResult { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#count; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut val: ScanResult = ::core::default::Default::default(); let mut_ref = &mut val; { mut_ref.decode_len_delimited(decoder)?; }; if let (Err(_), false) = (self.r#entries.pb_push(val), decoder.ignore_repeated_cap_err) { return Err(::micropb::DecodeError::Capacity); } } 3u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_ScanResult { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| (size + 1usize) * 16usize ) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#count; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_varint32(*val_ref as _)?; } } { for (i, val_ref) in self.r#entries.iter().enumerate() { encoder.encode_varint32(18u32)?; val_ref.encode_len_delimited(encoder)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#count; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { for (i, val_ref) in self.r#entries.iter().enumerate() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SoftAPConnectedSTA {} impl CtrlMsg_Req_SoftAPConnectedSTA {} impl ::micropb::MessageDecode for CtrlMsg_Req_SoftAPConnectedSTA { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SoftAPConnectedSTA { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SoftAPConnectedSTA { pub r#num: u32, pub r#stations: ::heapless::Vec, pub r#resp: i32, } impl CtrlMsg_Resp_SoftAPConnectedSTA { /// Return a reference to `num` #[inline] pub fn r#num(&self) -> &u32 { &self.r#num } /// Return a mutable reference to `num` #[inline] pub fn mut_num(&mut self) -> &mut u32 { &mut self.r#num } /// Set the value of `num` #[inline] pub fn set_num(&mut self, value: u32) -> &mut Self { self.r#num = value.into(); self } /// Builder method that sets the value of `num`. Useful for initializing the message. #[inline] pub fn init_num(mut self, value: u32) -> Self { self.r#num = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SoftAPConnectedSTA { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#num; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut val: ConnectedSTAList = ::core::default::Default::default(); let mut_ref = &mut val; { mut_ref.decode_len_delimited(decoder)?; }; if let (Err(_), false) = (self.r#stations.pb_push(val), decoder.ignore_repeated_cap_err) { return Err(::micropb::DecodeError::Capacity); } } 3u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SoftAPConnectedSTA { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| (size + 1usize) * 16usize ) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#num; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_varint32(*val_ref as _)?; } } { for (i, val_ref) in self.r#stations.iter().enumerate() { encoder.encode_varint32(18u32)?; val_ref.encode_len_delimited(encoder)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#num; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { for (i, val_ref) in self.r#stations.iter().enumerate() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_OTABegin {} impl CtrlMsg_Req_OTABegin {} impl ::micropb::MessageDecode for CtrlMsg_Req_OTABegin { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_OTABegin { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_OTABegin { pub r#resp: i32, } impl CtrlMsg_Resp_OTABegin { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_OTABegin { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_OTABegin { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_OTAWrite { pub r#ota_data: ::heapless::Vec, } impl CtrlMsg_Req_OTAWrite { /// Return a reference to `ota_data` #[inline] pub fn r#ota_data(&self) -> &::heapless::Vec { &self.r#ota_data } /// Return a mutable reference to `ota_data` #[inline] pub fn mut_ota_data(&mut self) -> &mut ::heapless::Vec { &mut self.r#ota_data } /// Set the value of `ota_data` #[inline] pub fn set_ota_data(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#ota_data = value.into(); self } /// Builder method that sets the value of `ota_data`. Useful for initializing the message. #[inline] pub fn init_ota_data(mut self, value: ::heapless::Vec) -> Self { self.r#ota_data = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_OTAWrite { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#ota_data; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_OTAWrite { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(258usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#ota_data; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#ota_data; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_OTAWrite { pub r#resp: i32, } impl CtrlMsg_Resp_OTAWrite { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_OTAWrite { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_OTAWrite { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_OTAEnd {} impl CtrlMsg_Req_OTAEnd {} impl ::micropb::MessageDecode for CtrlMsg_Req_OTAEnd { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_OTAEnd { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_OTAEnd { pub r#resp: i32, } impl CtrlMsg_Resp_OTAEnd { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_OTAEnd { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_OTAEnd { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_VendorIEData { pub r#element_id: i32, pub r#length: i32, pub r#vendor_oui: ::heapless::Vec, pub r#vendor_oui_type: i32, pub r#payload: ::heapless::Vec, } impl CtrlMsg_Req_VendorIEData { /// Return a reference to `element_id` #[inline] pub fn r#element_id(&self) -> &i32 { &self.r#element_id } /// Return a mutable reference to `element_id` #[inline] pub fn mut_element_id(&mut self) -> &mut i32 { &mut self.r#element_id } /// Set the value of `element_id` #[inline] pub fn set_element_id(&mut self, value: i32) -> &mut Self { self.r#element_id = value.into(); self } /// Builder method that sets the value of `element_id`. Useful for initializing the message. #[inline] pub fn init_element_id(mut self, value: i32) -> Self { self.r#element_id = value.into(); self } /// Return a reference to `length` #[inline] pub fn r#length(&self) -> &i32 { &self.r#length } /// Return a mutable reference to `length` #[inline] pub fn mut_length(&mut self) -> &mut i32 { &mut self.r#length } /// Set the value of `length` #[inline] pub fn set_length(&mut self, value: i32) -> &mut Self { self.r#length = value.into(); self } /// Builder method that sets the value of `length`. Useful for initializing the message. #[inline] pub fn init_length(mut self, value: i32) -> Self { self.r#length = value.into(); self } /// Return a reference to `vendor_oui` #[inline] pub fn r#vendor_oui(&self) -> &::heapless::Vec { &self.r#vendor_oui } /// Return a mutable reference to `vendor_oui` #[inline] pub fn mut_vendor_oui(&mut self) -> &mut ::heapless::Vec { &mut self.r#vendor_oui } /// Set the value of `vendor_oui` #[inline] pub fn set_vendor_oui(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#vendor_oui = value.into(); self } /// Builder method that sets the value of `vendor_oui`. Useful for initializing the message. #[inline] pub fn init_vendor_oui(mut self, value: ::heapless::Vec) -> Self { self.r#vendor_oui = value.into(); self } /// Return a reference to `vendor_oui_type` #[inline] pub fn r#vendor_oui_type(&self) -> &i32 { &self.r#vendor_oui_type } /// Return a mutable reference to `vendor_oui_type` #[inline] pub fn mut_vendor_oui_type(&mut self) -> &mut i32 { &mut self.r#vendor_oui_type } /// Set the value of `vendor_oui_type` #[inline] pub fn set_vendor_oui_type(&mut self, value: i32) -> &mut Self { self.r#vendor_oui_type = value.into(); self } /// Builder method that sets the value of `vendor_oui_type`. Useful for initializing the message. #[inline] pub fn init_vendor_oui_type(mut self, value: i32) -> Self { self.r#vendor_oui_type = value.into(); self } /// Return a reference to `payload` #[inline] pub fn r#payload(&self) -> &::heapless::Vec { &self.r#payload } /// Return a mutable reference to `payload` #[inline] pub fn mut_payload(&mut self) -> &mut ::heapless::Vec { &mut self.r#payload } /// Set the value of `payload` #[inline] pub fn set_payload(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#payload = value.into(); self } /// Builder method that sets the value of `payload`. Useful for initializing the message. #[inline] pub fn init_payload(mut self, value: ::heapless::Vec) -> Self { self.r#payload = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_VendorIEData { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#element_id; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#length; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#vendor_oui; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 4u32 => { let mut_ref = &mut self.r#vendor_oui_type; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#payload; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_VendorIEData { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(65usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#element_id; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#length; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#vendor_oui; if !val_ref.is_empty() { encoder.encode_varint32(26u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#vendor_oui_type; if *val_ref != 0 { encoder.encode_varint32(32u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#payload; if !val_ref.is_empty() { encoder.encode_varint32(42u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#element_id; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#length; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#vendor_oui; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#vendor_oui_type; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#payload; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SetSoftAPVendorSpecificIE { pub r#enable: bool, pub r#type: Ctrl_VendorIEType, pub r#idx: Ctrl_VendorIEID, /// *Note:* The presence of this field is tracked separately in the `_has` field. It's recommended to access this field via the accessor rather than directly. pub r#vendor_ie_data: CtrlMsg_Req_VendorIEData, /// Tracks presence of optional and message fields pub _has: CtrlMsg_Req_SetSoftAPVendorSpecificIE_::_Hazzer, } impl ::core::cmp::PartialEq for CtrlMsg_Req_SetSoftAPVendorSpecificIE { fn eq(&self, other: &Self) -> bool { let mut ret = true; ret &= (self.r#enable == other.r#enable); ret &= (self.r#type == other.r#type); ret &= (self.r#idx == other.r#idx); ret &= (self.r#vendor_ie_data() == other.r#vendor_ie_data()); ret } } impl CtrlMsg_Req_SetSoftAPVendorSpecificIE { /// Return a reference to `enable` #[inline] pub fn r#enable(&self) -> &bool { &self.r#enable } /// Return a mutable reference to `enable` #[inline] pub fn mut_enable(&mut self) -> &mut bool { &mut self.r#enable } /// Set the value of `enable` #[inline] pub fn set_enable(&mut self, value: bool) -> &mut Self { self.r#enable = value.into(); self } /// Builder method that sets the value of `enable`. Useful for initializing the message. #[inline] pub fn init_enable(mut self, value: bool) -> Self { self.r#enable = value.into(); self } /// Return a reference to `type` #[inline] pub fn r#type(&self) -> &Ctrl_VendorIEType { &self.r#type } /// Return a mutable reference to `type` #[inline] pub fn mut_type(&mut self) -> &mut Ctrl_VendorIEType { &mut self.r#type } /// Set the value of `type` #[inline] pub fn set_type(&mut self, value: Ctrl_VendorIEType) -> &mut Self { self.r#type = value.into(); self } /// Builder method that sets the value of `type`. Useful for initializing the message. #[inline] pub fn init_type(mut self, value: Ctrl_VendorIEType) -> Self { self.r#type = value.into(); self } /// Return a reference to `idx` #[inline] pub fn r#idx(&self) -> &Ctrl_VendorIEID { &self.r#idx } /// Return a mutable reference to `idx` #[inline] pub fn mut_idx(&mut self) -> &mut Ctrl_VendorIEID { &mut self.r#idx } /// Set the value of `idx` #[inline] pub fn set_idx(&mut self, value: Ctrl_VendorIEID) -> &mut Self { self.r#idx = value.into(); self } /// Builder method that sets the value of `idx`. Useful for initializing the message. #[inline] pub fn init_idx(mut self, value: Ctrl_VendorIEID) -> Self { self.r#idx = value.into(); self } /// Return a reference to `vendor_ie_data` as an `Option` #[inline] pub fn r#vendor_ie_data(&self) -> ::core::option::Option<&CtrlMsg_Req_VendorIEData> { self._has.r#vendor_ie_data().then_some(&self.r#vendor_ie_data) } /// Set the value and presence of `vendor_ie_data` #[inline] pub fn set_vendor_ie_data(&mut self, value: CtrlMsg_Req_VendorIEData) -> &mut Self { self._has.set_vendor_ie_data(); self.r#vendor_ie_data = value.into(); self } /// Return a mutable reference to `vendor_ie_data` as an `Option` #[inline] pub fn mut_vendor_ie_data(&mut self) -> ::core::option::Option<&mut CtrlMsg_Req_VendorIEData> { self._has.r#vendor_ie_data().then_some(&mut self.r#vendor_ie_data) } /// Clear the presence of `vendor_ie_data` #[inline] pub fn clear_vendor_ie_data(&mut self) -> &mut Self { self._has.clear_vendor_ie_data(); self } /// Take the value of `vendor_ie_data` and clear its presence #[inline] pub fn take_vendor_ie_data(&mut self) -> ::core::option::Option { let val = self ._has .r#vendor_ie_data() .then(|| ::core::mem::take(&mut self.r#vendor_ie_data)); self._has.clear_vendor_ie_data(); val } /// Builder method that sets the value of `vendor_ie_data`. Useful for initializing the message. #[inline] pub fn init_vendor_ie_data(mut self, value: CtrlMsg_Req_VendorIEData) -> Self { self.set_vendor_ie_data(value); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_SetSoftAPVendorSpecificIE { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#enable; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#type; { let val = decoder.decode_int32().map(|n| Ctrl_VendorIEType(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#idx; { let val = decoder.decode_int32().map(|n| Ctrl_VendorIEID(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#vendor_ie_data; { mut_ref.decode_len_delimited(decoder)?; }; self._has.set_vendor_ie_data(); } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SetSoftAPVendorSpecificIE { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(Ctrl_VendorIEType::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(Ctrl_VendorIEID::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 1usize ) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#enable; if *val_ref { encoder.encode_varint32(8u32)?; encoder.encode_bool(*val_ref)?; } } { let val_ref = &self.r#type; if val_ref.0 != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { let val_ref = &self.r#idx; if val_ref.0 != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { if let ::core::option::Option::Some(val_ref) = self.r#vendor_ie_data() { encoder.encode_varint32(34u32)?; val_ref.encode_len_delimited(encoder)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#enable; if *val_ref { size += 1usize + 1; } } { let val_ref = &self.r#type; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { let val_ref = &self.r#idx; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { if let ::core::option::Option::Some(val_ref) = self.r#vendor_ie_data() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } } size } } /// Inner types for `CtrlMsg_Req_SetSoftAPVendorSpecificIE` pub mod CtrlMsg_Req_SetSoftAPVendorSpecificIE_ { /// Compact bitfield for tracking presence of optional and message fields #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct _Hazzer([u8; 1]); impl _Hazzer { /// New hazzer with all fields set to off #[inline] pub const fn _new() -> Self { Self([0; 1]) } /// Query presence of `vendor_ie_data` #[inline] pub const fn r#vendor_ie_data(&self) -> bool { (self.0[0] & 1) != 0 } /// Set presence of `vendor_ie_data` #[inline] pub const fn set_vendor_ie_data(&mut self) -> &mut Self { let elem = &mut self.0[0]; *elem |= 1; self } /// Clear presence of `vendor_ie_data` #[inline] pub const fn clear_vendor_ie_data(&mut self) -> &mut Self { let elem = &mut self.0[0]; *elem &= !1; self } /// Builder method that sets the presence of `vendor_ie_data`. Useful for initializing the Hazzer. #[inline] pub const fn init_vendor_ie_data(mut self) -> Self { self.set_vendor_ie_data(); self } } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SetSoftAPVendorSpecificIE { pub r#resp: i32, } impl CtrlMsg_Resp_SetSoftAPVendorSpecificIE { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SetSoftAPVendorSpecificIE { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SetSoftAPVendorSpecificIE { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SetWifiMaxTxPower { pub r#wifi_max_tx_power: i32, } impl CtrlMsg_Req_SetWifiMaxTxPower { /// Return a reference to `wifi_max_tx_power` #[inline] pub fn r#wifi_max_tx_power(&self) -> &i32 { &self.r#wifi_max_tx_power } /// Return a mutable reference to `wifi_max_tx_power` #[inline] pub fn mut_wifi_max_tx_power(&mut self) -> &mut i32 { &mut self.r#wifi_max_tx_power } /// Set the value of `wifi_max_tx_power` #[inline] pub fn set_wifi_max_tx_power(&mut self, value: i32) -> &mut Self { self.r#wifi_max_tx_power = value.into(); self } /// Builder method that sets the value of `wifi_max_tx_power`. Useful for initializing the message. #[inline] pub fn init_wifi_max_tx_power(mut self, value: i32) -> Self { self.r#wifi_max_tx_power = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_SetWifiMaxTxPower { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#wifi_max_tx_power; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SetWifiMaxTxPower { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#wifi_max_tx_power; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#wifi_max_tx_power; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SetWifiMaxTxPower { pub r#resp: i32, } impl CtrlMsg_Resp_SetWifiMaxTxPower { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SetWifiMaxTxPower { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SetWifiMaxTxPower { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetWifiCurrTxPower {} impl CtrlMsg_Req_GetWifiCurrTxPower {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetWifiCurrTxPower { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetWifiCurrTxPower { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetWifiCurrTxPower { pub r#wifi_curr_tx_power: i32, pub r#resp: i32, } impl CtrlMsg_Resp_GetWifiCurrTxPower { /// Return a reference to `wifi_curr_tx_power` #[inline] pub fn r#wifi_curr_tx_power(&self) -> &i32 { &self.r#wifi_curr_tx_power } /// Return a mutable reference to `wifi_curr_tx_power` #[inline] pub fn mut_wifi_curr_tx_power(&mut self) -> &mut i32 { &mut self.r#wifi_curr_tx_power } /// Set the value of `wifi_curr_tx_power` #[inline] pub fn set_wifi_curr_tx_power(&mut self, value: i32) -> &mut Self { self.r#wifi_curr_tx_power = value.into(); self } /// Builder method that sets the value of `wifi_curr_tx_power`. Useful for initializing the message. #[inline] pub fn init_wifi_curr_tx_power(mut self, value: i32) -> Self { self.r#wifi_curr_tx_power = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetWifiCurrTxPower { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#wifi_curr_tx_power; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetWifiCurrTxPower { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#wifi_curr_tx_power; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#wifi_curr_tx_power; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_ConfigHeartbeat { pub r#enable: bool, pub r#duration: i32, } impl CtrlMsg_Req_ConfigHeartbeat { /// Return a reference to `enable` #[inline] pub fn r#enable(&self) -> &bool { &self.r#enable } /// Return a mutable reference to `enable` #[inline] pub fn mut_enable(&mut self) -> &mut bool { &mut self.r#enable } /// Set the value of `enable` #[inline] pub fn set_enable(&mut self, value: bool) -> &mut Self { self.r#enable = value.into(); self } /// Builder method that sets the value of `enable`. Useful for initializing the message. #[inline] pub fn init_enable(mut self, value: bool) -> Self { self.r#enable = value.into(); self } /// Return a reference to `duration` #[inline] pub fn r#duration(&self) -> &i32 { &self.r#duration } /// Return a mutable reference to `duration` #[inline] pub fn mut_duration(&mut self) -> &mut i32 { &mut self.r#duration } /// Set the value of `duration` #[inline] pub fn set_duration(&mut self, value: i32) -> &mut Self { self.r#duration = value.into(); self } /// Builder method that sets the value of `duration`. Useful for initializing the message. #[inline] pub fn init_duration(mut self, value: i32) -> Self { self.r#duration = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_ConfigHeartbeat { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#enable; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#duration; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_ConfigHeartbeat { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#enable; if *val_ref { encoder.encode_varint32(8u32)?; encoder.encode_bool(*val_ref)?; } } { let val_ref = &self.r#duration; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#enable; if *val_ref { size += 1usize + 1; } } { let val_ref = &self.r#duration; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_ConfigHeartbeat { pub r#resp: i32, } impl CtrlMsg_Resp_ConfigHeartbeat { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_ConfigHeartbeat { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_ConfigHeartbeat { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_EnableDisable { pub r#feature: u32, pub r#enable: bool, } impl CtrlMsg_Req_EnableDisable { /// Return a reference to `feature` #[inline] pub fn r#feature(&self) -> &u32 { &self.r#feature } /// Return a mutable reference to `feature` #[inline] pub fn mut_feature(&mut self) -> &mut u32 { &mut self.r#feature } /// Set the value of `feature` #[inline] pub fn set_feature(&mut self, value: u32) -> &mut Self { self.r#feature = value.into(); self } /// Builder method that sets the value of `feature`. Useful for initializing the message. #[inline] pub fn init_feature(mut self, value: u32) -> Self { self.r#feature = value.into(); self } /// Return a reference to `enable` #[inline] pub fn r#enable(&self) -> &bool { &self.r#enable } /// Return a mutable reference to `enable` #[inline] pub fn mut_enable(&mut self) -> &mut bool { &mut self.r#enable } /// Set the value of `enable` #[inline] pub fn set_enable(&mut self, value: bool) -> &mut Self { self.r#enable = value.into(); self } /// Builder method that sets the value of `enable`. Useful for initializing the message. #[inline] pub fn init_enable(mut self, value: bool) -> Self { self.r#enable = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_EnableDisable { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#feature; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#enable; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_EnableDisable { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#feature; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#enable; if *val_ref { encoder.encode_varint32(16u32)?; encoder.encode_bool(*val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#feature; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#enable; if *val_ref { size += 1usize + 1; } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_EnableDisable { pub r#resp: i32, } impl CtrlMsg_Resp_EnableDisable { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_EnableDisable { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_EnableDisable { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetFwVersion {} impl CtrlMsg_Req_GetFwVersion {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetFwVersion { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetFwVersion { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetFwVersion { pub r#resp: i32, pub r#name: ::heapless::String<32>, pub r#major1: u32, pub r#major2: u32, pub r#minor: u32, pub r#rev_patch1: u32, pub r#rev_patch2: u32, } impl CtrlMsg_Resp_GetFwVersion { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `name` #[inline] pub fn r#name(&self) -> &::heapless::String<32> { &self.r#name } /// Return a mutable reference to `name` #[inline] pub fn mut_name(&mut self) -> &mut ::heapless::String<32> { &mut self.r#name } /// Set the value of `name` #[inline] pub fn set_name(&mut self, value: ::heapless::String<32>) -> &mut Self { self.r#name = value.into(); self } /// Builder method that sets the value of `name`. Useful for initializing the message. #[inline] pub fn init_name(mut self, value: ::heapless::String<32>) -> Self { self.r#name = value.into(); self } /// Return a reference to `major1` #[inline] pub fn r#major1(&self) -> &u32 { &self.r#major1 } /// Return a mutable reference to `major1` #[inline] pub fn mut_major1(&mut self) -> &mut u32 { &mut self.r#major1 } /// Set the value of `major1` #[inline] pub fn set_major1(&mut self, value: u32) -> &mut Self { self.r#major1 = value.into(); self } /// Builder method that sets the value of `major1`. Useful for initializing the message. #[inline] pub fn init_major1(mut self, value: u32) -> Self { self.r#major1 = value.into(); self } /// Return a reference to `major2` #[inline] pub fn r#major2(&self) -> &u32 { &self.r#major2 } /// Return a mutable reference to `major2` #[inline] pub fn mut_major2(&mut self) -> &mut u32 { &mut self.r#major2 } /// Set the value of `major2` #[inline] pub fn set_major2(&mut self, value: u32) -> &mut Self { self.r#major2 = value.into(); self } /// Builder method that sets the value of `major2`. Useful for initializing the message. #[inline] pub fn init_major2(mut self, value: u32) -> Self { self.r#major2 = value.into(); self } /// Return a reference to `minor` #[inline] pub fn r#minor(&self) -> &u32 { &self.r#minor } /// Return a mutable reference to `minor` #[inline] pub fn mut_minor(&mut self) -> &mut u32 { &mut self.r#minor } /// Set the value of `minor` #[inline] pub fn set_minor(&mut self, value: u32) -> &mut Self { self.r#minor = value.into(); self } /// Builder method that sets the value of `minor`. Useful for initializing the message. #[inline] pub fn init_minor(mut self, value: u32) -> Self { self.r#minor = value.into(); self } /// Return a reference to `rev_patch1` #[inline] pub fn r#rev_patch1(&self) -> &u32 { &self.r#rev_patch1 } /// Return a mutable reference to `rev_patch1` #[inline] pub fn mut_rev_patch1(&mut self) -> &mut u32 { &mut self.r#rev_patch1 } /// Set the value of `rev_patch1` #[inline] pub fn set_rev_patch1(&mut self, value: u32) -> &mut Self { self.r#rev_patch1 = value.into(); self } /// Builder method that sets the value of `rev_patch1`. Useful for initializing the message. #[inline] pub fn init_rev_patch1(mut self, value: u32) -> Self { self.r#rev_patch1 = value.into(); self } /// Return a reference to `rev_patch2` #[inline] pub fn r#rev_patch2(&self) -> &u32 { &self.r#rev_patch2 } /// Return a mutable reference to `rev_patch2` #[inline] pub fn mut_rev_patch2(&mut self) -> &mut u32 { &mut self.r#rev_patch2 } /// Set the value of `rev_patch2` #[inline] pub fn set_rev_patch2(&mut self, value: u32) -> &mut Self { self.r#rev_patch2 = value.into(); self } /// Builder method that sets the value of `rev_patch2`. Useful for initializing the message. #[inline] pub fn init_rev_patch2(mut self, value: u32) -> Self { self.r#rev_patch2 = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetFwVersion { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#name; { decoder.decode_string(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#major1; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#major2; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#minor; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#rev_patch1; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 7u32 => { let mut_ref = &mut self.r#rev_patch2; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetFwVersion { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#name; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_string(val_ref)?; } } { let val_ref = &self.r#major1; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#major2; if *val_ref != 0 { encoder.encode_varint32(32u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#minor; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#rev_patch1; if *val_ref != 0 { encoder.encode_varint32(48u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#rev_patch2; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_varint32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#name; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#major1; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#major2; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#minor; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#rev_patch1; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#rev_patch2; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SetCountryCode { pub r#country: ::heapless::Vec, pub r#ieee80211d_enabled: bool, } impl CtrlMsg_Req_SetCountryCode { /// Return a reference to `country` #[inline] pub fn r#country(&self) -> &::heapless::Vec { &self.r#country } /// Return a mutable reference to `country` #[inline] pub fn mut_country(&mut self) -> &mut ::heapless::Vec { &mut self.r#country } /// Set the value of `country` #[inline] pub fn set_country(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#country = value.into(); self } /// Builder method that sets the value of `country`. Useful for initializing the message. #[inline] pub fn init_country(mut self, value: ::heapless::Vec) -> Self { self.r#country = value.into(); self } /// Return a reference to `ieee80211d_enabled` #[inline] pub fn r#ieee80211d_enabled(&self) -> &bool { &self.r#ieee80211d_enabled } /// Return a mutable reference to `ieee80211d_enabled` #[inline] pub fn mut_ieee80211d_enabled(&mut self) -> &mut bool { &mut self.r#ieee80211d_enabled } /// Set the value of `ieee80211d_enabled` #[inline] pub fn set_ieee80211d_enabled(&mut self, value: bool) -> &mut Self { self.r#ieee80211d_enabled = value.into(); self } /// Builder method that sets the value of `ieee80211d_enabled`. Useful for initializing the message. #[inline] pub fn init_ieee80211d_enabled(mut self, value: bool) -> Self { self.r#ieee80211d_enabled = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_SetCountryCode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#country; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 2u32 => { let mut_ref = &mut self.r#ieee80211d_enabled; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SetCountryCode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#country; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#ieee80211d_enabled; if *val_ref { encoder.encode_varint32(16u32)?; encoder.encode_bool(*val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#country; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#ieee80211d_enabled; if *val_ref { size += 1usize + 1; } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SetCountryCode { pub r#resp: i32, } impl CtrlMsg_Resp_SetCountryCode { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SetCountryCode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SetCountryCode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetCountryCode {} impl CtrlMsg_Req_GetCountryCode {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetCountryCode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetCountryCode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetCountryCode { pub r#resp: i32, pub r#country: ::heapless::Vec, } impl CtrlMsg_Resp_GetCountryCode { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `country` #[inline] pub fn r#country(&self) -> &::heapless::Vec { &self.r#country } /// Return a mutable reference to `country` #[inline] pub fn mut_country(&mut self) -> &mut ::heapless::Vec { &mut self.r#country } /// Set the value of `country` #[inline] pub fn set_country(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#country = value.into(); self } /// Builder method that sets the value of `country`. Useful for initializing the message. #[inline] pub fn init_country(mut self, value: ::heapless::Vec) -> Self { self.r#country = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetCountryCode { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#country; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetCountryCode { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#country; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#country; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_SetDhcpDnsStatus { pub r#iface: i32, pub r#net_link_up: i32, pub r#dhcp_up: i32, pub r#dhcp_ip: ::heapless::Vec, pub r#dhcp_nm: ::heapless::Vec, pub r#dhcp_gw: ::heapless::Vec, pub r#dns_up: i32, pub r#dns_ip: ::heapless::Vec, pub r#dns_type: i32, } impl CtrlMsg_Req_SetDhcpDnsStatus { /// Return a reference to `iface` #[inline] pub fn r#iface(&self) -> &i32 { &self.r#iface } /// Return a mutable reference to `iface` #[inline] pub fn mut_iface(&mut self) -> &mut i32 { &mut self.r#iface } /// Set the value of `iface` #[inline] pub fn set_iface(&mut self, value: i32) -> &mut Self { self.r#iface = value.into(); self } /// Builder method that sets the value of `iface`. Useful for initializing the message. #[inline] pub fn init_iface(mut self, value: i32) -> Self { self.r#iface = value.into(); self } /// Return a reference to `net_link_up` #[inline] pub fn r#net_link_up(&self) -> &i32 { &self.r#net_link_up } /// Return a mutable reference to `net_link_up` #[inline] pub fn mut_net_link_up(&mut self) -> &mut i32 { &mut self.r#net_link_up } /// Set the value of `net_link_up` #[inline] pub fn set_net_link_up(&mut self, value: i32) -> &mut Self { self.r#net_link_up = value.into(); self } /// Builder method that sets the value of `net_link_up`. Useful for initializing the message. #[inline] pub fn init_net_link_up(mut self, value: i32) -> Self { self.r#net_link_up = value.into(); self } /// Return a reference to `dhcp_up` #[inline] pub fn r#dhcp_up(&self) -> &i32 { &self.r#dhcp_up } /// Return a mutable reference to `dhcp_up` #[inline] pub fn mut_dhcp_up(&mut self) -> &mut i32 { &mut self.r#dhcp_up } /// Set the value of `dhcp_up` #[inline] pub fn set_dhcp_up(&mut self, value: i32) -> &mut Self { self.r#dhcp_up = value.into(); self } /// Builder method that sets the value of `dhcp_up`. Useful for initializing the message. #[inline] pub fn init_dhcp_up(mut self, value: i32) -> Self { self.r#dhcp_up = value.into(); self } /// Return a reference to `dhcp_ip` #[inline] pub fn r#dhcp_ip(&self) -> &::heapless::Vec { &self.r#dhcp_ip } /// Return a mutable reference to `dhcp_ip` #[inline] pub fn mut_dhcp_ip(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_ip } /// Set the value of `dhcp_ip` #[inline] pub fn set_dhcp_ip(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_ip = value.into(); self } /// Builder method that sets the value of `dhcp_ip`. Useful for initializing the message. #[inline] pub fn init_dhcp_ip(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_ip = value.into(); self } /// Return a reference to `dhcp_nm` #[inline] pub fn r#dhcp_nm(&self) -> &::heapless::Vec { &self.r#dhcp_nm } /// Return a mutable reference to `dhcp_nm` #[inline] pub fn mut_dhcp_nm(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_nm } /// Set the value of `dhcp_nm` #[inline] pub fn set_dhcp_nm(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_nm = value.into(); self } /// Builder method that sets the value of `dhcp_nm`. Useful for initializing the message. #[inline] pub fn init_dhcp_nm(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_nm = value.into(); self } /// Return a reference to `dhcp_gw` #[inline] pub fn r#dhcp_gw(&self) -> &::heapless::Vec { &self.r#dhcp_gw } /// Return a mutable reference to `dhcp_gw` #[inline] pub fn mut_dhcp_gw(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_gw } /// Set the value of `dhcp_gw` #[inline] pub fn set_dhcp_gw(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_gw = value.into(); self } /// Builder method that sets the value of `dhcp_gw`. Useful for initializing the message. #[inline] pub fn init_dhcp_gw(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_gw = value.into(); self } /// Return a reference to `dns_up` #[inline] pub fn r#dns_up(&self) -> &i32 { &self.r#dns_up } /// Return a mutable reference to `dns_up` #[inline] pub fn mut_dns_up(&mut self) -> &mut i32 { &mut self.r#dns_up } /// Set the value of `dns_up` #[inline] pub fn set_dns_up(&mut self, value: i32) -> &mut Self { self.r#dns_up = value.into(); self } /// Builder method that sets the value of `dns_up`. Useful for initializing the message. #[inline] pub fn init_dns_up(mut self, value: i32) -> Self { self.r#dns_up = value.into(); self } /// Return a reference to `dns_ip` #[inline] pub fn r#dns_ip(&self) -> &::heapless::Vec { &self.r#dns_ip } /// Return a mutable reference to `dns_ip` #[inline] pub fn mut_dns_ip(&mut self) -> &mut ::heapless::Vec { &mut self.r#dns_ip } /// Set the value of `dns_ip` #[inline] pub fn set_dns_ip(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dns_ip = value.into(); self } /// Builder method that sets the value of `dns_ip`. Useful for initializing the message. #[inline] pub fn init_dns_ip(mut self, value: ::heapless::Vec) -> Self { self.r#dns_ip = value.into(); self } /// Return a reference to `dns_type` #[inline] pub fn r#dns_type(&self) -> &i32 { &self.r#dns_type } /// Return a mutable reference to `dns_type` #[inline] pub fn mut_dns_type(&mut self) -> &mut i32 { &mut self.r#dns_type } /// Set the value of `dns_type` #[inline] pub fn set_dns_type(&mut self, value: i32) -> &mut Self { self.r#dns_type = value.into(); self } /// Builder method that sets the value of `dns_type`. Useful for initializing the message. #[inline] pub fn init_dns_type(mut self, value: i32) -> Self { self.r#dns_type = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_SetDhcpDnsStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#iface; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#net_link_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#dhcp_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#dhcp_ip; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 5u32 => { let mut_ref = &mut self.r#dhcp_nm; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 6u32 => { let mut_ref = &mut self.r#dhcp_gw; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 7u32 => { let mut_ref = &mut self.r#dns_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 8u32 => { let mut_ref = &mut self.r#dns_ip; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 9u32 => { let mut_ref = &mut self.r#dns_type; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_SetDhcpDnsStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#iface; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#net_link_up; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dhcp_up; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dhcp_ip; if !val_ref.is_empty() { encoder.encode_varint32(34u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dhcp_nm; if !val_ref.is_empty() { encoder.encode_varint32(42u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dhcp_gw; if !val_ref.is_empty() { encoder.encode_varint32(50u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dns_up; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dns_ip; if !val_ref.is_empty() { encoder.encode_varint32(66u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dns_type; if *val_ref != 0 { encoder.encode_varint32(72u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#iface; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#net_link_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dhcp_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dhcp_ip; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dhcp_nm; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dhcp_gw; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dns_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dns_ip; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dns_type; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_SetDhcpDnsStatus { pub r#resp: i32, } impl CtrlMsg_Resp_SetDhcpDnsStatus { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_SetDhcpDnsStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_SetDhcpDnsStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_GetDhcpDnsStatus {} impl CtrlMsg_Req_GetDhcpDnsStatus {} impl ::micropb::MessageDecode for CtrlMsg_Req_GetDhcpDnsStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_GetDhcpDnsStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_GetDhcpDnsStatus { pub r#resp: i32, pub r#iface: i32, pub r#net_link_up: i32, pub r#dhcp_up: i32, pub r#dhcp_ip: ::heapless::Vec, pub r#dhcp_nm: ::heapless::Vec, pub r#dhcp_gw: ::heapless::Vec, pub r#dns_up: i32, pub r#dns_ip: ::heapless::Vec, pub r#dns_type: i32, } impl CtrlMsg_Resp_GetDhcpDnsStatus { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `iface` #[inline] pub fn r#iface(&self) -> &i32 { &self.r#iface } /// Return a mutable reference to `iface` #[inline] pub fn mut_iface(&mut self) -> &mut i32 { &mut self.r#iface } /// Set the value of `iface` #[inline] pub fn set_iface(&mut self, value: i32) -> &mut Self { self.r#iface = value.into(); self } /// Builder method that sets the value of `iface`. Useful for initializing the message. #[inline] pub fn init_iface(mut self, value: i32) -> Self { self.r#iface = value.into(); self } /// Return a reference to `net_link_up` #[inline] pub fn r#net_link_up(&self) -> &i32 { &self.r#net_link_up } /// Return a mutable reference to `net_link_up` #[inline] pub fn mut_net_link_up(&mut self) -> &mut i32 { &mut self.r#net_link_up } /// Set the value of `net_link_up` #[inline] pub fn set_net_link_up(&mut self, value: i32) -> &mut Self { self.r#net_link_up = value.into(); self } /// Builder method that sets the value of `net_link_up`. Useful for initializing the message. #[inline] pub fn init_net_link_up(mut self, value: i32) -> Self { self.r#net_link_up = value.into(); self } /// Return a reference to `dhcp_up` #[inline] pub fn r#dhcp_up(&self) -> &i32 { &self.r#dhcp_up } /// Return a mutable reference to `dhcp_up` #[inline] pub fn mut_dhcp_up(&mut self) -> &mut i32 { &mut self.r#dhcp_up } /// Set the value of `dhcp_up` #[inline] pub fn set_dhcp_up(&mut self, value: i32) -> &mut Self { self.r#dhcp_up = value.into(); self } /// Builder method that sets the value of `dhcp_up`. Useful for initializing the message. #[inline] pub fn init_dhcp_up(mut self, value: i32) -> Self { self.r#dhcp_up = value.into(); self } /// Return a reference to `dhcp_ip` #[inline] pub fn r#dhcp_ip(&self) -> &::heapless::Vec { &self.r#dhcp_ip } /// Return a mutable reference to `dhcp_ip` #[inline] pub fn mut_dhcp_ip(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_ip } /// Set the value of `dhcp_ip` #[inline] pub fn set_dhcp_ip(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_ip = value.into(); self } /// Builder method that sets the value of `dhcp_ip`. Useful for initializing the message. #[inline] pub fn init_dhcp_ip(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_ip = value.into(); self } /// Return a reference to `dhcp_nm` #[inline] pub fn r#dhcp_nm(&self) -> &::heapless::Vec { &self.r#dhcp_nm } /// Return a mutable reference to `dhcp_nm` #[inline] pub fn mut_dhcp_nm(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_nm } /// Set the value of `dhcp_nm` #[inline] pub fn set_dhcp_nm(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_nm = value.into(); self } /// Builder method that sets the value of `dhcp_nm`. Useful for initializing the message. #[inline] pub fn init_dhcp_nm(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_nm = value.into(); self } /// Return a reference to `dhcp_gw` #[inline] pub fn r#dhcp_gw(&self) -> &::heapless::Vec { &self.r#dhcp_gw } /// Return a mutable reference to `dhcp_gw` #[inline] pub fn mut_dhcp_gw(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_gw } /// Set the value of `dhcp_gw` #[inline] pub fn set_dhcp_gw(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_gw = value.into(); self } /// Builder method that sets the value of `dhcp_gw`. Useful for initializing the message. #[inline] pub fn init_dhcp_gw(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_gw = value.into(); self } /// Return a reference to `dns_up` #[inline] pub fn r#dns_up(&self) -> &i32 { &self.r#dns_up } /// Return a mutable reference to `dns_up` #[inline] pub fn mut_dns_up(&mut self) -> &mut i32 { &mut self.r#dns_up } /// Set the value of `dns_up` #[inline] pub fn set_dns_up(&mut self, value: i32) -> &mut Self { self.r#dns_up = value.into(); self } /// Builder method that sets the value of `dns_up`. Useful for initializing the message. #[inline] pub fn init_dns_up(mut self, value: i32) -> Self { self.r#dns_up = value.into(); self } /// Return a reference to `dns_ip` #[inline] pub fn r#dns_ip(&self) -> &::heapless::Vec { &self.r#dns_ip } /// Return a mutable reference to `dns_ip` #[inline] pub fn mut_dns_ip(&mut self) -> &mut ::heapless::Vec { &mut self.r#dns_ip } /// Set the value of `dns_ip` #[inline] pub fn set_dns_ip(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dns_ip = value.into(); self } /// Builder method that sets the value of `dns_ip`. Useful for initializing the message. #[inline] pub fn init_dns_ip(mut self, value: ::heapless::Vec) -> Self { self.r#dns_ip = value.into(); self } /// Return a reference to `dns_type` #[inline] pub fn r#dns_type(&self) -> &i32 { &self.r#dns_type } /// Return a mutable reference to `dns_type` #[inline] pub fn mut_dns_type(&mut self) -> &mut i32 { &mut self.r#dns_type } /// Set the value of `dns_type` #[inline] pub fn set_dns_type(&mut self, value: i32) -> &mut Self { self.r#dns_type = value.into(); self } /// Builder method that sets the value of `dns_type`. Useful for initializing the message. #[inline] pub fn init_dns_type(mut self, value: i32) -> Self { self.r#dns_type = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_GetDhcpDnsStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#iface; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#net_link_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#dhcp_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#dhcp_ip; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 6u32 => { let mut_ref = &mut self.r#dhcp_nm; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 7u32 => { let mut_ref = &mut self.r#dhcp_gw; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 8u32 => { let mut_ref = &mut self.r#dns_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 9u32 => { let mut_ref = &mut self.r#dns_ip; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 10u32 => { let mut_ref = &mut self.r#dns_type; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_GetDhcpDnsStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#iface; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#net_link_up; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dhcp_up; if *val_ref != 0 { encoder.encode_varint32(32u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dhcp_ip; if !val_ref.is_empty() { encoder.encode_varint32(42u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dhcp_nm; if !val_ref.is_empty() { encoder.encode_varint32(50u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dhcp_gw; if !val_ref.is_empty() { encoder.encode_varint32(58u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dns_up; if *val_ref != 0 { encoder.encode_varint32(64u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dns_ip; if !val_ref.is_empty() { encoder.encode_varint32(74u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dns_type; if *val_ref != 0 { encoder.encode_varint32(80u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#iface; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#net_link_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dhcp_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dhcp_ip; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dhcp_nm; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dhcp_gw; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dns_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dns_ip; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dns_type; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } ///* Event structure * #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_ESPInit { pub r#init_data: ::heapless::Vec, } impl CtrlMsg_Event_ESPInit { /// Return a reference to `init_data` #[inline] pub fn r#init_data(&self) -> &::heapless::Vec { &self.r#init_data } /// Return a mutable reference to `init_data` #[inline] pub fn mut_init_data(&mut self) -> &mut ::heapless::Vec { &mut self.r#init_data } /// Set the value of `init_data` #[inline] pub fn set_init_data(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#init_data = value.into(); self } /// Builder method that sets the value of `init_data`. Useful for initializing the message. #[inline] pub fn init_init_data(mut self, value: ::heapless::Vec) -> Self { self.r#init_data = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_ESPInit { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#init_data; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_ESPInit { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(65usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#init_data; if !val_ref.is_empty() { encoder.encode_varint32(10u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#init_data; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_Heartbeat { pub r#hb_num: i32, } impl CtrlMsg_Event_Heartbeat { /// Return a reference to `hb_num` #[inline] pub fn r#hb_num(&self) -> &i32 { &self.r#hb_num } /// Return a mutable reference to `hb_num` #[inline] pub fn mut_hb_num(&mut self) -> &mut i32 { &mut self.r#hb_num } /// Set the value of `hb_num` #[inline] pub fn set_hb_num(&mut self, value: i32) -> &mut Self { self.r#hb_num = value.into(); self } /// Builder method that sets the value of `hb_num`. Useful for initializing the message. #[inline] pub fn init_hb_num(mut self, value: i32) -> Self { self.r#hb_num = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_Heartbeat { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#hb_num; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_Heartbeat { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#hb_num; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#hb_num; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_StationDisconnectFromAP { pub r#resp: i32, pub r#ssid: ::heapless::Vec, pub r#ssid_len: u32, pub r#bssid: ::heapless::Vec, pub r#reason: u32, pub r#rssi: i32, } impl CtrlMsg_Event_StationDisconnectFromAP { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::Vec { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::Vec) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `ssid_len` #[inline] pub fn r#ssid_len(&self) -> &u32 { &self.r#ssid_len } /// Return a mutable reference to `ssid_len` #[inline] pub fn mut_ssid_len(&mut self) -> &mut u32 { &mut self.r#ssid_len } /// Set the value of `ssid_len` #[inline] pub fn set_ssid_len(&mut self, value: u32) -> &mut Self { self.r#ssid_len = value.into(); self } /// Builder method that sets the value of `ssid_len`. Useful for initializing the message. #[inline] pub fn init_ssid_len(mut self, value: u32) -> Self { self.r#ssid_len = value.into(); self } /// Return a reference to `bssid` #[inline] pub fn r#bssid(&self) -> &::heapless::Vec { &self.r#bssid } /// Return a mutable reference to `bssid` #[inline] pub fn mut_bssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#bssid } /// Set the value of `bssid` #[inline] pub fn set_bssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#bssid = value.into(); self } /// Builder method that sets the value of `bssid`. Useful for initializing the message. #[inline] pub fn init_bssid(mut self, value: ::heapless::Vec) -> Self { self.r#bssid = value.into(); self } /// Return a reference to `reason` #[inline] pub fn r#reason(&self) -> &u32 { &self.r#reason } /// Return a mutable reference to `reason` #[inline] pub fn mut_reason(&mut self) -> &mut u32 { &mut self.r#reason } /// Set the value of `reason` #[inline] pub fn set_reason(&mut self, value: u32) -> &mut Self { self.r#reason = value.into(); self } /// Builder method that sets the value of `reason`. Useful for initializing the message. #[inline] pub fn init_reason(mut self, value: u32) -> Self { self.r#reason = value.into(); self } /// Return a reference to `rssi` #[inline] pub fn r#rssi(&self) -> &i32 { &self.r#rssi } /// Return a mutable reference to `rssi` #[inline] pub fn mut_rssi(&mut self) -> &mut i32 { &mut self.r#rssi } /// Set the value of `rssi` #[inline] pub fn set_rssi(&mut self, value: i32) -> &mut Self { self.r#rssi = value.into(); self } /// Builder method that sets the value of `rssi`. Useful for initializing the message. #[inline] pub fn init_rssi(mut self, value: i32) -> Self { self.r#rssi = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_StationDisconnectFromAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#ssid_len; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#bssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 5u32 => { let mut_ref = &mut self.r#reason; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#rssi; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_StationDisconnectFromAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#ssid_len; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { encoder.encode_varint32(34u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#reason; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#rssi; if *val_ref != 0 { encoder.encode_varint32(48u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#ssid_len; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#reason; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#rssi; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_StationConnectedToAP { pub r#resp: i32, pub r#ssid: ::heapless::Vec, pub r#ssid_len: u32, pub r#bssid: ::heapless::Vec, pub r#channel: u32, pub r#authmode: i32, pub r#aid: i32, } impl CtrlMsg_Event_StationConnectedToAP { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `ssid` #[inline] pub fn r#ssid(&self) -> &::heapless::Vec { &self.r#ssid } /// Return a mutable reference to `ssid` #[inline] pub fn mut_ssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#ssid } /// Set the value of `ssid` #[inline] pub fn set_ssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#ssid = value.into(); self } /// Builder method that sets the value of `ssid`. Useful for initializing the message. #[inline] pub fn init_ssid(mut self, value: ::heapless::Vec) -> Self { self.r#ssid = value.into(); self } /// Return a reference to `ssid_len` #[inline] pub fn r#ssid_len(&self) -> &u32 { &self.r#ssid_len } /// Return a mutable reference to `ssid_len` #[inline] pub fn mut_ssid_len(&mut self) -> &mut u32 { &mut self.r#ssid_len } /// Set the value of `ssid_len` #[inline] pub fn set_ssid_len(&mut self, value: u32) -> &mut Self { self.r#ssid_len = value.into(); self } /// Builder method that sets the value of `ssid_len`. Useful for initializing the message. #[inline] pub fn init_ssid_len(mut self, value: u32) -> Self { self.r#ssid_len = value.into(); self } /// Return a reference to `bssid` #[inline] pub fn r#bssid(&self) -> &::heapless::Vec { &self.r#bssid } /// Return a mutable reference to `bssid` #[inline] pub fn mut_bssid(&mut self) -> &mut ::heapless::Vec { &mut self.r#bssid } /// Set the value of `bssid` #[inline] pub fn set_bssid(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#bssid = value.into(); self } /// Builder method that sets the value of `bssid`. Useful for initializing the message. #[inline] pub fn init_bssid(mut self, value: ::heapless::Vec) -> Self { self.r#bssid = value.into(); self } /// Return a reference to `channel` #[inline] pub fn r#channel(&self) -> &u32 { &self.r#channel } /// Return a mutable reference to `channel` #[inline] pub fn mut_channel(&mut self) -> &mut u32 { &mut self.r#channel } /// Set the value of `channel` #[inline] pub fn set_channel(&mut self, value: u32) -> &mut Self { self.r#channel = value.into(); self } /// Builder method that sets the value of `channel`. Useful for initializing the message. #[inline] pub fn init_channel(mut self, value: u32) -> Self { self.r#channel = value.into(); self } /// Return a reference to `authmode` #[inline] pub fn r#authmode(&self) -> &i32 { &self.r#authmode } /// Return a mutable reference to `authmode` #[inline] pub fn mut_authmode(&mut self) -> &mut i32 { &mut self.r#authmode } /// Set the value of `authmode` #[inline] pub fn set_authmode(&mut self, value: i32) -> &mut Self { self.r#authmode = value.into(); self } /// Builder method that sets the value of `authmode`. Useful for initializing the message. #[inline] pub fn init_authmode(mut self, value: i32) -> Self { self.r#authmode = value.into(); self } /// Return a reference to `aid` #[inline] pub fn r#aid(&self) -> &i32 { &self.r#aid } /// Return a mutable reference to `aid` #[inline] pub fn mut_aid(&mut self) -> &mut i32 { &mut self.r#aid } /// Set the value of `aid` #[inline] pub fn set_aid(&mut self, value: i32) -> &mut Self { self.r#aid = value.into(); self } /// Builder method that sets the value of `aid`. Useful for initializing the message. #[inline] pub fn init_aid(mut self, value: i32) -> Self { self.r#aid = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_StationConnectedToAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#ssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#ssid_len; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#bssid; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 5u32 => { let mut_ref = &mut self.r#channel; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 6u32 => { let mut_ref = &mut self.r#authmode; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 7u32 => { let mut_ref = &mut self.r#aid; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_StationConnectedToAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#ssid; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#ssid_len; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { encoder.encode_varint32(34u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#channel; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#authmode; if *val_ref != 0 { encoder.encode_varint32(48u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#aid; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#ssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#ssid_len; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#bssid; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#channel; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#authmode; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#aid; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_StationDisconnectFromESPSoftAP { pub r#resp: i32, pub r#mac: ::heapless::Vec, pub r#aid: u32, pub r#is_mesh_child: bool, pub r#reason: u32, } impl CtrlMsg_Event_StationDisconnectFromESPSoftAP { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `aid` #[inline] pub fn r#aid(&self) -> &u32 { &self.r#aid } /// Return a mutable reference to `aid` #[inline] pub fn mut_aid(&mut self) -> &mut u32 { &mut self.r#aid } /// Set the value of `aid` #[inline] pub fn set_aid(&mut self, value: u32) -> &mut Self { self.r#aid = value.into(); self } /// Builder method that sets the value of `aid`. Useful for initializing the message. #[inline] pub fn init_aid(mut self, value: u32) -> Self { self.r#aid = value.into(); self } /// Return a reference to `is_mesh_child` #[inline] pub fn r#is_mesh_child(&self) -> &bool { &self.r#is_mesh_child } /// Return a mutable reference to `is_mesh_child` #[inline] pub fn mut_is_mesh_child(&mut self) -> &mut bool { &mut self.r#is_mesh_child } /// Set the value of `is_mesh_child` #[inline] pub fn set_is_mesh_child(&mut self, value: bool) -> &mut Self { self.r#is_mesh_child = value.into(); self } /// Builder method that sets the value of `is_mesh_child`. Useful for initializing the message. #[inline] pub fn init_is_mesh_child(mut self, value: bool) -> Self { self.r#is_mesh_child = value.into(); self } /// Return a reference to `reason` #[inline] pub fn r#reason(&self) -> &u32 { &self.r#reason } /// Return a mutable reference to `reason` #[inline] pub fn mut_reason(&mut self) -> &mut u32 { &mut self.r#reason } /// Set the value of `reason` #[inline] pub fn set_reason(&mut self, value: u32) -> &mut Self { self.r#reason = value.into(); self } /// Builder method that sets the value of `reason`. Useful for initializing the message. #[inline] pub fn init_reason(mut self, value: u32) -> Self { self.r#reason = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_StationDisconnectFromESPSoftAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#aid; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#is_mesh_child; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } 5u32 => { let mut_ref = &mut self.r#reason; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_StationDisconnectFromESPSoftAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#aid; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#is_mesh_child; if *val_ref { encoder.encode_varint32(32u32)?; encoder.encode_bool(*val_ref)?; } } { let val_ref = &self.r#reason; if *val_ref != 0 { encoder.encode_varint32(40u32)?; encoder.encode_varint32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#aid; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#is_mesh_child; if *val_ref { size += 1usize + 1; } } { let val_ref = &self.r#reason; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_StationConnectedToESPSoftAP { pub r#resp: i32, pub r#mac: ::heapless::Vec, pub r#aid: u32, pub r#is_mesh_child: bool, } impl CtrlMsg_Event_StationConnectedToESPSoftAP { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `mac` #[inline] pub fn r#mac(&self) -> &::heapless::Vec { &self.r#mac } /// Return a mutable reference to `mac` #[inline] pub fn mut_mac(&mut self) -> &mut ::heapless::Vec { &mut self.r#mac } /// Set the value of `mac` #[inline] pub fn set_mac(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#mac = value.into(); self } /// Builder method that sets the value of `mac`. Useful for initializing the message. #[inline] pub fn init_mac(mut self, value: ::heapless::Vec) -> Self { self.r#mac = value.into(); self } /// Return a reference to `aid` #[inline] pub fn r#aid(&self) -> &u32 { &self.r#aid } /// Return a mutable reference to `aid` #[inline] pub fn mut_aid(&mut self) -> &mut u32 { &mut self.r#aid } /// Set the value of `aid` #[inline] pub fn set_aid(&mut self, value: u32) -> &mut Self { self.r#aid = value.into(); self } /// Builder method that sets the value of `aid`. Useful for initializing the message. #[inline] pub fn init_aid(mut self, value: u32) -> Self { self.r#aid = value.into(); self } /// Return a reference to `is_mesh_child` #[inline] pub fn r#is_mesh_child(&self) -> &bool { &self.r#is_mesh_child } /// Return a mutable reference to `is_mesh_child` #[inline] pub fn mut_is_mesh_child(&mut self) -> &mut bool { &mut self.r#is_mesh_child } /// Set the value of `is_mesh_child` #[inline] pub fn set_is_mesh_child(&mut self, value: bool) -> &mut Self { self.r#is_mesh_child = value.into(); self } /// Builder method that sets the value of `is_mesh_child`. Useful for initializing the message. #[inline] pub fn init_is_mesh_child(mut self, value: bool) -> Self { self.r#is_mesh_child = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_StationConnectedToESPSoftAP { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#mac; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 3u32 => { let mut_ref = &mut self.r#aid; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#is_mesh_child; { let val = decoder.decode_bool()?; let val_ref = &val; if *val_ref { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_StationConnectedToESPSoftAP { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(1usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#aid; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#is_mesh_child; if *val_ref { encoder.encode_varint32(32u32)?; encoder.encode_bool(*val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#mac; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#aid; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#is_mesh_child; if *val_ref { size += 1usize + 1; } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_SetDhcpDnsStatus { pub r#iface: i32, pub r#net_link_up: i32, pub r#dhcp_up: i32, pub r#dhcp_ip: ::heapless::Vec, pub r#dhcp_nm: ::heapless::Vec, pub r#dhcp_gw: ::heapless::Vec, pub r#dns_up: i32, pub r#dns_ip: ::heapless::Vec, pub r#dns_type: i32, pub r#resp: i32, } impl CtrlMsg_Event_SetDhcpDnsStatus { /// Return a reference to `iface` #[inline] pub fn r#iface(&self) -> &i32 { &self.r#iface } /// Return a mutable reference to `iface` #[inline] pub fn mut_iface(&mut self) -> &mut i32 { &mut self.r#iface } /// Set the value of `iface` #[inline] pub fn set_iface(&mut self, value: i32) -> &mut Self { self.r#iface = value.into(); self } /// Builder method that sets the value of `iface`. Useful for initializing the message. #[inline] pub fn init_iface(mut self, value: i32) -> Self { self.r#iface = value.into(); self } /// Return a reference to `net_link_up` #[inline] pub fn r#net_link_up(&self) -> &i32 { &self.r#net_link_up } /// Return a mutable reference to `net_link_up` #[inline] pub fn mut_net_link_up(&mut self) -> &mut i32 { &mut self.r#net_link_up } /// Set the value of `net_link_up` #[inline] pub fn set_net_link_up(&mut self, value: i32) -> &mut Self { self.r#net_link_up = value.into(); self } /// Builder method that sets the value of `net_link_up`. Useful for initializing the message. #[inline] pub fn init_net_link_up(mut self, value: i32) -> Self { self.r#net_link_up = value.into(); self } /// Return a reference to `dhcp_up` #[inline] pub fn r#dhcp_up(&self) -> &i32 { &self.r#dhcp_up } /// Return a mutable reference to `dhcp_up` #[inline] pub fn mut_dhcp_up(&mut self) -> &mut i32 { &mut self.r#dhcp_up } /// Set the value of `dhcp_up` #[inline] pub fn set_dhcp_up(&mut self, value: i32) -> &mut Self { self.r#dhcp_up = value.into(); self } /// Builder method that sets the value of `dhcp_up`. Useful for initializing the message. #[inline] pub fn init_dhcp_up(mut self, value: i32) -> Self { self.r#dhcp_up = value.into(); self } /// Return a reference to `dhcp_ip` #[inline] pub fn r#dhcp_ip(&self) -> &::heapless::Vec { &self.r#dhcp_ip } /// Return a mutable reference to `dhcp_ip` #[inline] pub fn mut_dhcp_ip(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_ip } /// Set the value of `dhcp_ip` #[inline] pub fn set_dhcp_ip(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_ip = value.into(); self } /// Builder method that sets the value of `dhcp_ip`. Useful for initializing the message. #[inline] pub fn init_dhcp_ip(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_ip = value.into(); self } /// Return a reference to `dhcp_nm` #[inline] pub fn r#dhcp_nm(&self) -> &::heapless::Vec { &self.r#dhcp_nm } /// Return a mutable reference to `dhcp_nm` #[inline] pub fn mut_dhcp_nm(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_nm } /// Set the value of `dhcp_nm` #[inline] pub fn set_dhcp_nm(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_nm = value.into(); self } /// Builder method that sets the value of `dhcp_nm`. Useful for initializing the message. #[inline] pub fn init_dhcp_nm(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_nm = value.into(); self } /// Return a reference to `dhcp_gw` #[inline] pub fn r#dhcp_gw(&self) -> &::heapless::Vec { &self.r#dhcp_gw } /// Return a mutable reference to `dhcp_gw` #[inline] pub fn mut_dhcp_gw(&mut self) -> &mut ::heapless::Vec { &mut self.r#dhcp_gw } /// Set the value of `dhcp_gw` #[inline] pub fn set_dhcp_gw(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dhcp_gw = value.into(); self } /// Builder method that sets the value of `dhcp_gw`. Useful for initializing the message. #[inline] pub fn init_dhcp_gw(mut self, value: ::heapless::Vec) -> Self { self.r#dhcp_gw = value.into(); self } /// Return a reference to `dns_up` #[inline] pub fn r#dns_up(&self) -> &i32 { &self.r#dns_up } /// Return a mutable reference to `dns_up` #[inline] pub fn mut_dns_up(&mut self) -> &mut i32 { &mut self.r#dns_up } /// Set the value of `dns_up` #[inline] pub fn set_dns_up(&mut self, value: i32) -> &mut Self { self.r#dns_up = value.into(); self } /// Builder method that sets the value of `dns_up`. Useful for initializing the message. #[inline] pub fn init_dns_up(mut self, value: i32) -> Self { self.r#dns_up = value.into(); self } /// Return a reference to `dns_ip` #[inline] pub fn r#dns_ip(&self) -> &::heapless::Vec { &self.r#dns_ip } /// Return a mutable reference to `dns_ip` #[inline] pub fn mut_dns_ip(&mut self) -> &mut ::heapless::Vec { &mut self.r#dns_ip } /// Set the value of `dns_ip` #[inline] pub fn set_dns_ip(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#dns_ip = value.into(); self } /// Builder method that sets the value of `dns_ip`. Useful for initializing the message. #[inline] pub fn init_dns_ip(mut self, value: ::heapless::Vec) -> Self { self.r#dns_ip = value.into(); self } /// Return a reference to `dns_type` #[inline] pub fn r#dns_type(&self) -> &i32 { &self.r#dns_type } /// Return a mutable reference to `dns_type` #[inline] pub fn mut_dns_type(&mut self) -> &mut i32 { &mut self.r#dns_type } /// Set the value of `dns_type` #[inline] pub fn set_dns_type(&mut self, value: i32) -> &mut Self { self.r#dns_type = value.into(); self } /// Builder method that sets the value of `dns_type`. Useful for initializing the message. #[inline] pub fn init_dns_type(mut self, value: i32) -> Self { self.r#dns_type = value.into(); self } /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_SetDhcpDnsStatus { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#iface; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#net_link_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#dhcp_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#dhcp_ip; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 5u32 => { let mut_ref = &mut self.r#dhcp_nm; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 6u32 => { let mut_ref = &mut self.r#dhcp_gw; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 7u32 => { let mut_ref = &mut self.r#dns_up; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 8u32 => { let mut_ref = &mut self.r#dns_ip; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } 9u32 => { let mut_ref = &mut self.r#dns_type; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 10u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_SetDhcpDnsStatus { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#iface; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#net_link_up; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dhcp_up; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dhcp_ip; if !val_ref.is_empty() { encoder.encode_varint32(34u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dhcp_nm; if !val_ref.is_empty() { encoder.encode_varint32(42u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dhcp_gw; if !val_ref.is_empty() { encoder.encode_varint32(50u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dns_up; if *val_ref != 0 { encoder.encode_varint32(56u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#dns_ip; if !val_ref.is_empty() { encoder.encode_varint32(66u32)?; encoder.encode_bytes(val_ref)?; } } { let val_ref = &self.r#dns_type; if *val_ref != 0 { encoder.encode_varint32(72u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(80u32)?; encoder.encode_int32(*val_ref as _)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#iface; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#net_link_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dhcp_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dhcp_ip; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dhcp_nm; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dhcp_gw; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dns_up; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#dns_ip; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } { let val_ref = &self.r#dns_type; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } size } } /// Add Custom RPC message structures after existing message structures to make it easily notice #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Req_CustomRpcUnserialisedMsg { pub r#custom_msg_id: u32, pub r#data: ::heapless::Vec, } impl CtrlMsg_Req_CustomRpcUnserialisedMsg { /// Return a reference to `custom_msg_id` #[inline] pub fn r#custom_msg_id(&self) -> &u32 { &self.r#custom_msg_id } /// Return a mutable reference to `custom_msg_id` #[inline] pub fn mut_custom_msg_id(&mut self) -> &mut u32 { &mut self.r#custom_msg_id } /// Set the value of `custom_msg_id` #[inline] pub fn set_custom_msg_id(&mut self, value: u32) -> &mut Self { self.r#custom_msg_id = value.into(); self } /// Builder method that sets the value of `custom_msg_id`. Useful for initializing the message. #[inline] pub fn init_custom_msg_id(mut self, value: u32) -> Self { self.r#custom_msg_id = value.into(); self } /// Return a reference to `data` #[inline] pub fn r#data(&self) -> &::heapless::Vec { &self.r#data } /// Return a mutable reference to `data` #[inline] pub fn mut_data(&mut self) -> &mut ::heapless::Vec { &mut self.r#data } /// Set the value of `data` #[inline] pub fn set_data(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#data = value.into(); self } /// Builder method that sets the value of `data`. Useful for initializing the message. #[inline] pub fn init_data(mut self, value: ::heapless::Vec) -> Self { self.r#data = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Req_CustomRpcUnserialisedMsg { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#custom_msg_id; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#data; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Req_CustomRpcUnserialisedMsg { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#custom_msg_id; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#data; if !val_ref.is_empty() { encoder.encode_varint32(18u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#custom_msg_id; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#data; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Resp_CustomRpcUnserialisedMsg { pub r#resp: i32, pub r#custom_msg_id: u32, pub r#data: ::heapless::Vec, } impl CtrlMsg_Resp_CustomRpcUnserialisedMsg { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `custom_msg_id` #[inline] pub fn r#custom_msg_id(&self) -> &u32 { &self.r#custom_msg_id } /// Return a mutable reference to `custom_msg_id` #[inline] pub fn mut_custom_msg_id(&mut self) -> &mut u32 { &mut self.r#custom_msg_id } /// Set the value of `custom_msg_id` #[inline] pub fn set_custom_msg_id(&mut self, value: u32) -> &mut Self { self.r#custom_msg_id = value.into(); self } /// Builder method that sets the value of `custom_msg_id`. Useful for initializing the message. #[inline] pub fn init_custom_msg_id(mut self, value: u32) -> Self { self.r#custom_msg_id = value.into(); self } /// Return a reference to `data` #[inline] pub fn r#data(&self) -> &::heapless::Vec { &self.r#data } /// Return a mutable reference to `data` #[inline] pub fn mut_data(&mut self) -> &mut ::heapless::Vec { &mut self.r#data } /// Set the value of `data` #[inline] pub fn set_data(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#data = value.into(); self } /// Builder method that sets the value of `data`. Useful for initializing the message. #[inline] pub fn init_data(mut self, value: ::heapless::Vec) -> Self { self.r#data = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Resp_CustomRpcUnserialisedMsg { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#custom_msg_id; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#data; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Resp_CustomRpcUnserialisedMsg { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#custom_msg_id; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#data; if !val_ref.is_empty() { encoder.encode_varint32(26u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#custom_msg_id; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#data; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg_Event_CustomRpcUnserialisedMsg { pub r#resp: i32, pub r#custom_evt_id: u32, pub r#data: ::heapless::Vec, } impl CtrlMsg_Event_CustomRpcUnserialisedMsg { /// Return a reference to `resp` #[inline] pub fn r#resp(&self) -> &i32 { &self.r#resp } /// Return a mutable reference to `resp` #[inline] pub fn mut_resp(&mut self) -> &mut i32 { &mut self.r#resp } /// Set the value of `resp` #[inline] pub fn set_resp(&mut self, value: i32) -> &mut Self { self.r#resp = value.into(); self } /// Builder method that sets the value of `resp`. Useful for initializing the message. #[inline] pub fn init_resp(mut self, value: i32) -> Self { self.r#resp = value.into(); self } /// Return a reference to `custom_evt_id` #[inline] pub fn r#custom_evt_id(&self) -> &u32 { &self.r#custom_evt_id } /// Return a mutable reference to `custom_evt_id` #[inline] pub fn mut_custom_evt_id(&mut self) -> &mut u32 { &mut self.r#custom_evt_id } /// Set the value of `custom_evt_id` #[inline] pub fn set_custom_evt_id(&mut self, value: u32) -> &mut Self { self.r#custom_evt_id = value.into(); self } /// Builder method that sets the value of `custom_evt_id`. Useful for initializing the message. #[inline] pub fn init_custom_evt_id(mut self, value: u32) -> Self { self.r#custom_evt_id = value.into(); self } /// Return a reference to `data` #[inline] pub fn r#data(&self) -> &::heapless::Vec { &self.r#data } /// Return a mutable reference to `data` #[inline] pub fn mut_data(&mut self) -> &mut ::heapless::Vec { &mut self.r#data } /// Set the value of `data` #[inline] pub fn set_data(&mut self, value: ::heapless::Vec) -> &mut Self { self.r#data = value.into(); self } /// Builder method that sets the value of `data`. Useful for initializing the message. #[inline] pub fn init_data(mut self, value: ::heapless::Vec) -> Self { self.r#data = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg_Event_CustomRpcUnserialisedMsg { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#resp; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#custom_evt_id; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#data; { decoder.decode_bytes(mut_ref, ::micropb::Presence::Implicit)?; }; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg_Event_CustomRpcUnserialisedMsg { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(33usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#resp; if *val_ref != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#custom_evt_id; if *val_ref != 0 { encoder.encode_varint32(16u32)?; encoder.encode_varint32(*val_ref as _)?; } } { let val_ref = &self.r#data; if !val_ref.is_empty() { encoder.encode_varint32(26u32)?; encoder.encode_bytes(val_ref)?; } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#resp; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#custom_evt_id; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } { let val_ref = &self.r#data; if !val_ref.is_empty() { size += 1usize + ::micropb::size::sizeof_len_record(val_ref.len()); } } size } } #[derive(Debug, Default, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsg { /// msg_type could be req, resp or Event pub r#msg_type: CtrlMsgType, /// msg id pub r#msg_id: CtrlMsgId, /// UID of message pub r#uid: i32, /// Request/response type: sync or async pub r#req_resp_type: u32, /// union of all msg ids pub r#payload: ::core::option::Option, } impl CtrlMsg { /// Return a reference to `msg_type` #[inline] pub fn r#msg_type(&self) -> &CtrlMsgType { &self.r#msg_type } /// Return a mutable reference to `msg_type` #[inline] pub fn mut_msg_type(&mut self) -> &mut CtrlMsgType { &mut self.r#msg_type } /// Set the value of `msg_type` #[inline] pub fn set_msg_type(&mut self, value: CtrlMsgType) -> &mut Self { self.r#msg_type = value.into(); self } /// Builder method that sets the value of `msg_type`. Useful for initializing the message. #[inline] pub fn init_msg_type(mut self, value: CtrlMsgType) -> Self { self.r#msg_type = value.into(); self } /// Return a reference to `msg_id` #[inline] pub fn r#msg_id(&self) -> &CtrlMsgId { &self.r#msg_id } /// Return a mutable reference to `msg_id` #[inline] pub fn mut_msg_id(&mut self) -> &mut CtrlMsgId { &mut self.r#msg_id } /// Set the value of `msg_id` #[inline] pub fn set_msg_id(&mut self, value: CtrlMsgId) -> &mut Self { self.r#msg_id = value.into(); self } /// Builder method that sets the value of `msg_id`. Useful for initializing the message. #[inline] pub fn init_msg_id(mut self, value: CtrlMsgId) -> Self { self.r#msg_id = value.into(); self } /// Return a reference to `uid` #[inline] pub fn r#uid(&self) -> &i32 { &self.r#uid } /// Return a mutable reference to `uid` #[inline] pub fn mut_uid(&mut self) -> &mut i32 { &mut self.r#uid } /// Set the value of `uid` #[inline] pub fn set_uid(&mut self, value: i32) -> &mut Self { self.r#uid = value.into(); self } /// Builder method that sets the value of `uid`. Useful for initializing the message. #[inline] pub fn init_uid(mut self, value: i32) -> Self { self.r#uid = value.into(); self } /// Return a reference to `req_resp_type` #[inline] pub fn r#req_resp_type(&self) -> &u32 { &self.r#req_resp_type } /// Return a mutable reference to `req_resp_type` #[inline] pub fn mut_req_resp_type(&mut self) -> &mut u32 { &mut self.r#req_resp_type } /// Set the value of `req_resp_type` #[inline] pub fn set_req_resp_type(&mut self, value: u32) -> &mut Self { self.r#req_resp_type = value.into(); self } /// Builder method that sets the value of `req_resp_type`. Useful for initializing the message. #[inline] pub fn init_req_resp_type(mut self, value: u32) -> Self { self.r#req_resp_type = value.into(); self } } impl ::micropb::MessageDecode for CtrlMsg { fn decode( &mut self, decoder: &mut ::micropb::PbDecoder, len: usize, ) -> Result<(), ::micropb::DecodeError> { use ::micropb::{FieldDecode, PbBytes, PbMap, PbString, PbVec}; let before = decoder.bytes_read(); while decoder.bytes_read() - before < len { let tag = decoder.decode_tag()?; match tag.field_num() { 0 => return Err(::micropb::DecodeError::ZeroField), 1u32 => { let mut_ref = &mut self.r#msg_type; { let val = decoder.decode_int32().map(|n| CtrlMsgType(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 2u32 => { let mut_ref = &mut self.r#msg_id; { let val = decoder.decode_int32().map(|n| CtrlMsgId(n as _))?; let val_ref = &val; if val_ref.0 != 0 { *mut_ref = val as _; } }; } 3u32 => { let mut_ref = &mut self.r#uid; { let val = decoder.decode_int32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 4u32 => { let mut_ref = &mut self.r#req_resp_type; { let val = decoder.decode_varint32()?; let val_ref = &val; if *val_ref != 0 { *mut_ref = val as _; } }; } 101u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetMacAddress(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetMacAddress( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 102u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetMacAddress(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetMacAddress( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 103u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetWifiMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetWifiMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 104u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetWifiMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetWifiMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 105u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqScanApList(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqScanApList( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 106u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetApConfig(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetApConfig( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 107u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqConnectAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqConnectAp( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 108u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqDisconnectAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqDisconnectAp( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 109u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetSoftapConfig(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetSoftapConfig( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 110u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetSoftapVendorSpecificIe(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetSoftapVendorSpecificIe( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 111u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqStartSoftap(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqStartSoftap( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 112u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSoftapConnectedStasList(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSoftapConnectedStasList( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 113u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqStopSoftap(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqStopSoftap( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 114u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetPowerSaveMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetPowerSaveMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 115u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetPowerSaveMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetPowerSaveMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 116u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqOtaBegin(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqOtaBegin( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 117u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqOtaWrite(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqOtaWrite( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 118u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqOtaEnd(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqOtaEnd( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 119u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetWifiMaxTxPower(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetWifiMaxTxPower( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 120u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetWifiCurrTxPower(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetWifiCurrTxPower( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 121u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqConfigHeartbeat(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqConfigHeartbeat( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 122u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqEnableDisableFeat(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqEnableDisableFeat( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 123u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetFwVersion(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetFwVersion( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 124u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetCountryCode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetCountryCode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 125u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetCountryCode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetCountryCode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 126u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqSetDhcpDnsStatus(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqSetDhcpDnsStatus( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 127u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqGetDhcpDnsStatus(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqGetDhcpDnsStatus( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 128u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::ReqCustomRpcUnserialisedMsg(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::ReqCustomRpcUnserialisedMsg( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 201u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetMacAddress(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetMacAddress( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 202u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetMacAddress(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSetMacAddress( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 203u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetWifiMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetWifiMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 204u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetWifiMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSetWifiMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 205u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespScanApList(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespScanApList( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 206u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetApConfig(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetApConfig( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 207u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespConnectAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespConnectAp( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 208u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespDisconnectAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespDisconnectAp( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 209u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetSoftapConfig(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetSoftapConfig( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 210u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetSoftapVendorSpecificIe(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some( CtrlMsg_::Payload::RespSetSoftapVendorSpecificIe(::core::default::Default::default()), ); }; mut_ref.decode_len_delimited(decoder)?; } 211u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespStartSoftap(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespStartSoftap( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 212u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSoftapConnectedStasList(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSoftapConnectedStasList( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 213u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespStopSoftap(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespStopSoftap( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 214u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetPowerSaveMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSetPowerSaveMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 215u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetPowerSaveMode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetPowerSaveMode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 216u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespOtaBegin(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespOtaBegin( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 217u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespOtaWrite(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespOtaWrite( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 218u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespOtaEnd(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespOtaEnd( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 219u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetWifiMaxTxPower(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSetWifiMaxTxPower( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 220u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetWifiCurrTxPower(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetWifiCurrTxPower( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 221u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespConfigHeartbeat(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespConfigHeartbeat( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 222u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespEnableDisableFeat(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespEnableDisableFeat( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 223u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetFwVersion(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetFwVersion( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 224u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetCountryCode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSetCountryCode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 225u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetCountryCode(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetCountryCode( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 226u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespSetDhcpDnsStatus(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespSetDhcpDnsStatus( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 227u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespGetDhcpDnsStatus(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespGetDhcpDnsStatus( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 228u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::RespCustomRpcUnserialisedMsg(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::RespCustomRpcUnserialisedMsg( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 301u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventEspInit(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::EventEspInit( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 302u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventHeartbeat(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::EventHeartbeat( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 303u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventStationDisconnectFromAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::EventStationDisconnectFromAp( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 304u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventStationDisconnectFromEspSoftAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some( CtrlMsg_::Payload::EventStationDisconnectFromEspSoftAp(::core::default::Default::default()), ); }; mut_ref.decode_len_delimited(decoder)?; } 305u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventStationConnectedToAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::EventStationConnectedToAp( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 306u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventStationConnectedToEspSoftAp(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some( CtrlMsg_::Payload::EventStationConnectedToEspSoftAp(::core::default::Default::default()), ); }; mut_ref.decode_len_delimited(decoder)?; } 307u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventSetDhcpDnsStatus(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some(CtrlMsg_::Payload::EventSetDhcpDnsStatus( ::core::default::Default::default(), )); }; mut_ref.decode_len_delimited(decoder)?; } 308u32 => { let mut_ref = loop { if let ::core::option::Option::Some(variant) = &mut self.r#payload { if let CtrlMsg_::Payload::EventCustomRpcUnserialisedMsg(variant) = &mut *variant { break &mut *variant; } } self.r#payload = ::core::option::Option::Some( CtrlMsg_::Payload::EventCustomRpcUnserialisedMsg(::core::default::Default::default()), ); }; mut_ref.decode_len_delimited(decoder)?; } _ => { decoder.skip_wire_value(tag.wire_type())?; } } } Ok(()) } } impl ::micropb::MessageEncode for CtrlMsg { const MAX_SIZE: ::core::result::Result = 'msg: { let mut max_size = 0; match ::micropb::const_map!(::core::result::Result::Ok(CtrlMsgType::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(CtrlMsgId::_MAX_SIZE), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(10usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match ::micropb::const_map!(::core::result::Result::Ok(5usize), |size| size + 1usize) { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } match 'oneof: { let mut max_size = 0; match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!(::MAX_SIZE, |size| { ::micropb::size::sizeof_len_record(size) }), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } match ::micropb::const_map!( ::micropb::const_map!( ::MAX_SIZE, |size| ::micropb::size::sizeof_len_record(size) ), |size| size + 2usize ) { ::core::result::Result::Ok(size) => { if size > max_size { max_size = size; } } ::core::result::Result::Err(err) => { break 'oneof (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) } { ::core::result::Result::Ok(size) => { max_size += size; } ::core::result::Result::Err(err) => { break 'msg (::core::result::Result::::Err(err)); } } ::core::result::Result::Ok(max_size) }; fn encode( &self, encoder: &mut ::micropb::PbEncoder, ) -> Result<(), IMPL_MICROPB_WRITE::Error> { use ::micropb::{FieldEncode, PbMap}; { let val_ref = &self.r#msg_type; if val_ref.0 != 0 { encoder.encode_varint32(8u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { let val_ref = &self.r#msg_id; if val_ref.0 != 0 { encoder.encode_varint32(16u32)?; encoder.encode_int32(val_ref.0 as _)?; } } { let val_ref = &self.r#uid; if *val_ref != 0 { encoder.encode_varint32(24u32)?; encoder.encode_int32(*val_ref as _)?; } } { let val_ref = &self.r#req_resp_type; if *val_ref != 0 { encoder.encode_varint32(32u32)?; encoder.encode_varint32(*val_ref as _)?; } } if let Some(oneof) = &self.r#payload { match &*oneof { CtrlMsg_::Payload::ReqGetMacAddress(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(810u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetMacAddress(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(818u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetWifiMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(826u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetWifiMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(834u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqScanApList(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(842u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetApConfig(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(850u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqConnectAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(858u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqDisconnectAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(866u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetSoftapConfig(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(874u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetSoftapVendorSpecificIe(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(882u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqStartSoftap(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(890u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSoftapConnectedStasList(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(898u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqStopSoftap(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(906u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(914u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(922u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqOtaBegin(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(930u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqOtaWrite(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(938u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqOtaEnd(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(946u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetWifiMaxTxPower(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(954u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetWifiCurrTxPower(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(962u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqConfigHeartbeat(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(970u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqEnableDisableFeat(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(978u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetFwVersion(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(986u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetCountryCode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(994u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetCountryCode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1002u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqSetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1010u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqGetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1018u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::ReqCustomRpcUnserialisedMsg(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1026u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetMacAddress(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1610u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetMacAddress(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1618u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetWifiMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1626u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetWifiMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1634u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespScanApList(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1642u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetApConfig(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1650u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespConnectAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1658u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespDisconnectAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1666u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetSoftapConfig(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1674u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetSoftapVendorSpecificIe(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1682u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespStartSoftap(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1690u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSoftapConnectedStasList(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1698u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespStopSoftap(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1706u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1714u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1722u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespOtaBegin(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1730u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespOtaWrite(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1738u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespOtaEnd(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1746u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetWifiMaxTxPower(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1754u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetWifiCurrTxPower(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1762u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespConfigHeartbeat(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1770u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespEnableDisableFeat(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1778u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetFwVersion(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1786u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetCountryCode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1794u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetCountryCode(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1802u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespSetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1810u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespGetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1818u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::RespCustomRpcUnserialisedMsg(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(1826u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventEspInit(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2410u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventHeartbeat(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2418u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventStationDisconnectFromAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2426u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventStationDisconnectFromEspSoftAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2434u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventStationConnectedToAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2442u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventStationConnectedToEspSoftAp(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2450u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventSetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2458u32)?; val_ref.encode_len_delimited(encoder)?; } CtrlMsg_::Payload::EventCustomRpcUnserialisedMsg(val_ref) => { let val_ref = &*val_ref; encoder.encode_varint32(2466u32)?; val_ref.encode_len_delimited(encoder)?; } } } Ok(()) } fn compute_size(&self) -> usize { use ::micropb::{FieldEncode, PbMap}; let mut size = 0; { let val_ref = &self.r#msg_type; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { let val_ref = &self.r#msg_id; if val_ref.0 != 0 { size += 1usize + ::micropb::size::sizeof_int32(val_ref.0 as _); } } { let val_ref = &self.r#uid; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_int32(*val_ref as _); } } { let val_ref = &self.r#req_resp_type; if *val_ref != 0 { size += 1usize + ::micropb::size::sizeof_varint32(*val_ref as _); } } if let Some(oneof) = &self.r#payload { match &*oneof { CtrlMsg_::Payload::ReqGetMacAddress(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetMacAddress(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetWifiMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetWifiMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqScanApList(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetApConfig(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqConnectAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqDisconnectAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetSoftapConfig(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetSoftapVendorSpecificIe(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqStartSoftap(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSoftapConnectedStasList(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqStopSoftap(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqOtaBegin(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqOtaWrite(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqOtaEnd(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetWifiMaxTxPower(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetWifiCurrTxPower(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqConfigHeartbeat(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqEnableDisableFeat(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetFwVersion(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetCountryCode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetCountryCode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqSetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqGetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::ReqCustomRpcUnserialisedMsg(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetMacAddress(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetMacAddress(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetWifiMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetWifiMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespScanApList(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetApConfig(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespConnectAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespDisconnectAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetSoftapConfig(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetSoftapVendorSpecificIe(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespStartSoftap(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSoftapConnectedStasList(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespStopSoftap(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetPowerSaveMode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespOtaBegin(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespOtaWrite(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespOtaEnd(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetWifiMaxTxPower(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetWifiCurrTxPower(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespConfigHeartbeat(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespEnableDisableFeat(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetFwVersion(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetCountryCode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetCountryCode(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespSetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespGetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::RespCustomRpcUnserialisedMsg(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventEspInit(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventHeartbeat(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventStationDisconnectFromAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventStationDisconnectFromEspSoftAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventStationConnectedToAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventStationConnectedToEspSoftAp(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventSetDhcpDnsStatus(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } CtrlMsg_::Payload::EventCustomRpcUnserialisedMsg(val_ref) => { let val_ref = &*val_ref; size += 2usize + ::micropb::size::sizeof_len_record(val_ref.compute_size()); } } } size } } /// Inner types for `CtrlMsg` pub mod CtrlMsg_ { /// union of all msg ids #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Payload { ///* Requests * ReqGetMacAddress(super::CtrlMsg_Req_GetMacAddress), ReqSetMacAddress(super::CtrlMsg_Req_SetMacAddress), ReqGetWifiMode(super::CtrlMsg_Req_GetMode), ReqSetWifiMode(super::CtrlMsg_Req_SetMode), ReqScanApList(super::CtrlMsg_Req_ScanResult), ReqGetApConfig(super::CtrlMsg_Req_GetAPConfig), ReqConnectAp(super::CtrlMsg_Req_ConnectAP), ReqDisconnectAp(super::CtrlMsg_Req_GetStatus), ReqGetSoftapConfig(super::CtrlMsg_Req_GetSoftAPConfig), ReqSetSoftapVendorSpecificIe(super::CtrlMsg_Req_SetSoftAPVendorSpecificIE), ReqStartSoftap(super::CtrlMsg_Req_StartSoftAP), ReqSoftapConnectedStasList(super::CtrlMsg_Req_SoftAPConnectedSTA), ReqStopSoftap(super::CtrlMsg_Req_GetStatus), ReqSetPowerSaveMode(super::CtrlMsg_Req_SetMode), ReqGetPowerSaveMode(super::CtrlMsg_Req_GetMode), ReqOtaBegin(super::CtrlMsg_Req_OTABegin), ReqOtaWrite(super::CtrlMsg_Req_OTAWrite), ReqOtaEnd(super::CtrlMsg_Req_OTAEnd), ReqSetWifiMaxTxPower(super::CtrlMsg_Req_SetWifiMaxTxPower), ReqGetWifiCurrTxPower(super::CtrlMsg_Req_GetWifiCurrTxPower), ReqConfigHeartbeat(super::CtrlMsg_Req_ConfigHeartbeat), ReqEnableDisableFeat(super::CtrlMsg_Req_EnableDisable), ReqGetFwVersion(super::CtrlMsg_Req_GetFwVersion), ReqSetCountryCode(super::CtrlMsg_Req_SetCountryCode), ReqGetCountryCode(super::CtrlMsg_Req_GetCountryCode), ReqSetDhcpDnsStatus(super::CtrlMsg_Req_SetDhcpDnsStatus), ReqGetDhcpDnsStatus(super::CtrlMsg_Req_GetDhcpDnsStatus), ReqCustomRpcUnserialisedMsg(super::CtrlMsg_Req_CustomRpcUnserialisedMsg), ///* Responses * RespGetMacAddress(super::CtrlMsg_Resp_GetMacAddress), RespSetMacAddress(super::CtrlMsg_Resp_SetMacAddress), RespGetWifiMode(super::CtrlMsg_Resp_GetMode), RespSetWifiMode(super::CtrlMsg_Resp_SetMode), RespScanApList(super::CtrlMsg_Resp_ScanResult), RespGetApConfig(super::CtrlMsg_Resp_GetAPConfig), RespConnectAp(super::CtrlMsg_Resp_ConnectAP), RespDisconnectAp(super::CtrlMsg_Resp_GetStatus), RespGetSoftapConfig(super::CtrlMsg_Resp_GetSoftAPConfig), RespSetSoftapVendorSpecificIe(super::CtrlMsg_Resp_SetSoftAPVendorSpecificIE), RespStartSoftap(super::CtrlMsg_Resp_StartSoftAP), RespSoftapConnectedStasList(super::CtrlMsg_Resp_SoftAPConnectedSTA), RespStopSoftap(super::CtrlMsg_Resp_GetStatus), RespSetPowerSaveMode(super::CtrlMsg_Resp_SetMode), RespGetPowerSaveMode(super::CtrlMsg_Resp_GetMode), RespOtaBegin(super::CtrlMsg_Resp_OTABegin), RespOtaWrite(super::CtrlMsg_Resp_OTAWrite), RespOtaEnd(super::CtrlMsg_Resp_OTAEnd), RespSetWifiMaxTxPower(super::CtrlMsg_Resp_SetWifiMaxTxPower), RespGetWifiCurrTxPower(super::CtrlMsg_Resp_GetWifiCurrTxPower), RespConfigHeartbeat(super::CtrlMsg_Resp_ConfigHeartbeat), RespEnableDisableFeat(super::CtrlMsg_Resp_EnableDisable), RespGetFwVersion(super::CtrlMsg_Resp_GetFwVersion), RespSetCountryCode(super::CtrlMsg_Resp_SetCountryCode), RespGetCountryCode(super::CtrlMsg_Resp_GetCountryCode), RespSetDhcpDnsStatus(super::CtrlMsg_Resp_SetDhcpDnsStatus), RespGetDhcpDnsStatus(super::CtrlMsg_Resp_GetDhcpDnsStatus), RespCustomRpcUnserialisedMsg(super::CtrlMsg_Resp_CustomRpcUnserialisedMsg), ///* Notifications * EventEspInit(super::CtrlMsg_Event_ESPInit), EventHeartbeat(super::CtrlMsg_Event_Heartbeat), EventStationDisconnectFromAp(super::CtrlMsg_Event_StationDisconnectFromAP), EventStationDisconnectFromEspSoftAp(super::CtrlMsg_Event_StationDisconnectFromESPSoftAP), EventStationConnectedToAp(super::CtrlMsg_Event_StationConnectedToAP), EventStationConnectedToEspSoftAp(super::CtrlMsg_Event_StationConnectedToESPSoftAP), EventSetDhcpDnsStatus(super::CtrlMsg_Event_SetDhcpDnsStatus), EventCustomRpcUnserialisedMsg(super::CtrlMsg_Event_CustomRpcUnserialisedMsg), } } /// Enums similar to ESP IDF #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_VendorIEType(pub i32); impl Ctrl_VendorIEType { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const Beacon: Self = Self(0); pub const ProbeReq: Self = Self(1); pub const ProbeResp: Self = Self(2); pub const AssocReq: Self = Self(3); pub const AssocResp: Self = Self(4); } impl core::default::Default for Ctrl_VendorIEType { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_VendorIEType { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_VendorIEID(pub i32); impl Ctrl_VendorIEID { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const Id0: Self = Self(0); pub const Id1: Self = Self(1); } impl core::default::Default for Ctrl_VendorIEID { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_VendorIEID { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_WifiMode(pub i32); impl Ctrl_WifiMode { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const None: Self = Self(0); pub const Sta: Self = Self(1); pub const Ap: Self = Self(2); pub const Apsta: Self = Self(3); } impl core::default::Default for Ctrl_WifiMode { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_WifiMode { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_WifiBw(pub i32); impl Ctrl_WifiBw { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const BwInvalid: Self = Self(0); pub const Ht20: Self = Self(1); pub const Ht40: Self = Self(2); } impl core::default::Default for Ctrl_WifiBw { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_WifiBw { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_WifiPowerSave(pub i32); impl Ctrl_WifiPowerSave { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const NoPs: Self = Self(0); pub const MinModem: Self = Self(1); pub const MaxModem: Self = Self(2); pub const PsInvalid: Self = Self(3); } impl core::default::Default for Ctrl_WifiPowerSave { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_WifiPowerSave { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_WifiSecProt(pub i32); impl Ctrl_WifiSecProt { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const Open: Self = Self(0); pub const Wep: Self = Self(1); pub const WpaPsk: Self = Self(2); pub const Wpa2Psk: Self = Self(3); pub const WpaWpa2Psk: Self = Self(4); pub const Wpa2Enterprise: Self = Self(5); pub const Wpa3Psk: Self = Self(6); pub const Wpa2Wpa3Psk: Self = Self(7); } impl core::default::Default for Ctrl_WifiSecProt { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_WifiSecProt { fn from(val: i32) -> Self { Self(val) } } /// enums for Control path #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Ctrl_Status(pub i32); impl Ctrl_Status { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const Connected: Self = Self(0); pub const NotConnected: Self = Self(1); pub const NoApFound: Self = Self(2); pub const ConnectionFail: Self = Self(3); pub const InvalidArgument: Self = Self(4); pub const OutOfRange: Self = Self(5); } impl core::default::Default for Ctrl_Status { fn default() -> Self { Self(0) } } impl core::convert::From for Ctrl_Status { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsgType(pub i32); impl CtrlMsgType { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const MsgTypeInvalid: Self = Self(0); pub const Req: Self = Self(1); pub const Resp: Self = Self(2); pub const Event: Self = Self(3); pub const MsgTypeMax: Self = Self(4); } impl core::default::Default for CtrlMsgType { fn default() -> Self { Self(0) } } impl core::convert::From for CtrlMsgType { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CtrlMsgId(pub i32); impl CtrlMsgId { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const MsgIdInvalid: Self = Self(0); ///* Request Msgs * pub const ReqBase: Self = Self(100); pub const ReqGetMacAddress: Self = Self(101); pub const ReqSetMacAddress: Self = Self(102); pub const ReqGetWifiMode: Self = Self(103); pub const ReqSetWifiMode: Self = Self(104); pub const ReqGetApScanList: Self = Self(105); pub const ReqGetApConfig: Self = Self(106); pub const ReqConnectAp: Self = Self(107); pub const ReqDisconnectAp: Self = Self(108); pub const ReqGetSoftApConfig: Self = Self(109); pub const ReqSetSoftApVendorSpecificIe: Self = Self(110); pub const ReqStartSoftAp: Self = Self(111); pub const ReqGetSoftApConnectedStaList: Self = Self(112); pub const ReqStopSoftAp: Self = Self(113); pub const ReqSetPowerSaveMode: Self = Self(114); pub const ReqGetPowerSaveMode: Self = Self(115); pub const ReqOtaBegin: Self = Self(116); pub const ReqOtaWrite: Self = Self(117); pub const ReqOtaEnd: Self = Self(118); pub const ReqSetWifiMaxTxPower: Self = Self(119); pub const ReqGetWifiCurrTxPower: Self = Self(120); pub const ReqConfigHeartbeat: Self = Self(121); pub const ReqEnableDisable: Self = Self(122); pub const ReqGetFwVersion: Self = Self(123); pub const ReqSetCountryCode: Self = Self(124); pub const ReqGetCountryCode: Self = Self(125); pub const ReqSetDhcpDnsStatus: Self = Self(126); pub const ReqGetDhcpDnsStatus: Self = Self(127); pub const ReqCustomRpcUnserialisedMsg: Self = Self(128); /// Add new control path command response before Req_Max /// and update Req_Max pub const ReqMax: Self = Self(129); ///* Response Msgs * pub const RespBase: Self = Self(200); pub const RespGetMacAddress: Self = Self(201); pub const RespSetMacAddress: Self = Self(202); pub const RespGetWifiMode: Self = Self(203); pub const RespSetWifiMode: Self = Self(204); pub const RespGetApScanList: Self = Self(205); pub const RespGetApConfig: Self = Self(206); pub const RespConnectAp: Self = Self(207); pub const RespDisconnectAp: Self = Self(208); pub const RespGetSoftApConfig: Self = Self(209); pub const RespSetSoftApVendorSpecificIe: Self = Self(210); pub const RespStartSoftAp: Self = Self(211); pub const RespGetSoftApConnectedStaList: Self = Self(212); pub const RespStopSoftAp: Self = Self(213); pub const RespSetPowerSaveMode: Self = Self(214); pub const RespGetPowerSaveMode: Self = Self(215); pub const RespOtaBegin: Self = Self(216); pub const RespOtaWrite: Self = Self(217); pub const RespOtaEnd: Self = Self(218); pub const RespSetWifiMaxTxPower: Self = Self(219); pub const RespGetWifiCurrTxPower: Self = Self(220); pub const RespConfigHeartbeat: Self = Self(221); pub const RespEnableDisable: Self = Self(222); pub const RespGetFwVersion: Self = Self(223); pub const RespSetCountryCode: Self = Self(224); pub const RespGetCountryCode: Self = Self(225); pub const RespSetDhcpDnsStatus: Self = Self(226); pub const RespGetDhcpDnsStatus: Self = Self(227); pub const RespCustomRpcUnserialisedMsg: Self = Self(228); /// Add new control path command response before Resp_Max /// and update Resp_Max pub const RespMax: Self = Self(229); ///* Event Msgs * pub const EventBase: Self = Self(300); pub const EventEspInit: Self = Self(301); pub const EventHeartbeat: Self = Self(302); pub const EventStationDisconnectFromAp: Self = Self(303); pub const EventStationDisconnectFromEspSoftAp: Self = Self(304); pub const EventStationConnectedToAp: Self = Self(305); pub const EventStationConnectedToEspSoftAp: Self = Self(306); pub const EventSetDhcpDnsStatus: Self = Self(307); pub const EventCustomRpcUnserialisedMsg: Self = Self(308); /// Add new control path command notification before Event_Max /// and update Event_Max pub const EventMax: Self = Self(309); } impl core::default::Default for CtrlMsgId { fn default() -> Self { Self(0) } } impl core::convert::From for CtrlMsgId { fn from(val: i32) -> Self { Self(val) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct HostedFeature(pub i32); impl HostedFeature { /// Maximum encoded size of the enum pub const _MAX_SIZE: usize = 10usize; pub const HostedInvalidFeature: Self = Self(0); pub const HostedWifi: Self = Self(1); pub const HostedBluetooth: Self = Self(2); pub const HostedIsNetworkSplitOn: Self = Self(3); } impl core::default::Default for HostedFeature { fn default() -> Self { Self(0) } } impl core::convert::From for HostedFeature { fn from(val: i32) -> Self { Self(val) } } ================================================ FILE: embassy-net-nrf91/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.2.0 - 2026-03-10 - Signal link state based on link attach to prevent too early transmit confusing LTE modem. - Upgrade embassy-sync to 0.8.0 - Update embassy-net-driver-channel to 0.4.0 ## 0.1.0 - 2025-12-15 - Initial release ================================================ FILE: embassy-net-nrf91/Cargo.toml ================================================ [package] name = "embassy-net-nrf91" version = "0.2.0" edition = "2024" 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" publish = true [features] defmt = ["dep:defmt", "heapless/defmt", "embassy-time/defmt"] log = ["dep:log"] [dependencies] defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } # nrf-pac = { version = "0.2.0", git = "https://github.com/embassy-rs/nrf-pac.git", rev = "074935b9b24ab302fe812f91725937f57cd082cf" } nrf-pac = "0.3.0" cortex-m = "0.7.7" embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-net-driver-channel = { version = "0.4.0", path = "../embassy-net-driver-channel" } heapless = "0.9" embedded-io = { version = "0.7.1" } at-commands = "0.5.4" [package.metadata.embassy] build = [ {target = "thumbv7em-none-eabi", features = ["defmt", "nrf-pac/nrf9160"]} ] [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", "nrf-pac/nrf9160"] [package.metadata.docs.rs] features = ["defmt", "nrf-pac/nrf9160"] ================================================ FILE: embassy-net-nrf91/README.md ================================================ # 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. ================================================ FILE: embassy-net-nrf91/src/context.rs ================================================ //! 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_net_driver_channel::driver::LinkState; 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?; self.control.set_link_state(LinkState::Up); let mut fd = self.control.open_raw_socket().await; reattach(&status); loop { if !self.attached().await? { self.control.set_link_state(LinkState::Down); trace!("detached"); self.control.close_raw_socket(fd).await; let status = self.wait_attached().await?; trace!("attached"); self.control.set_link_state(LinkState::Up); fd = self.control.open_raw_socket().await; reattach(&status); } Timer::after(Duration::from_secs(10)).await; } } } ================================================ FILE: embassy-net-nrf91/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net-nrf91/src/lib.rs ================================================ #![no_std] #![allow(unsafe_op_in_unsafe_fn)] #![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::{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::{Ordering, compiler_fence, fence}; 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 trace!(" shmem_ptr = {}, shmem_len = {}", shmem_ptr, shmem_len); assert!(shmem_len != 0, "shmem length must not be zero"); 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 pointer must be 8kb-aligned" ); 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..."); trace!( " SPU_REGION_SIZE={}, shmem_ptr=0x{:08X}, shmem_len={}", SPU_REGION_SIZE, shmem_ptr as usize, shmem_len ); let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE; let region_end = region_start + shmem_len / SPU_REGION_SIZE; trace!(" region_start={}, region_end={}", region_start, region_end); 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, }; trace!( " Allocator: start=0x{:08X}, end=0x{:08X}", alloc.start as usize, alloc.end as usize ); let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() }); let rx = alloc.alloc_bytes(RX_SIZE); trace!(" RX buffer at {}, size={}", rx.as_ptr(), RX_SIZE); let trace = alloc.alloc_bytes(TRACE_SIZE); trace!(" Trace buffer at {}, size={}", trace.as_ptr(), 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; let ipc = pac::IPC_NS; ipc.gpmem(0).write_value(cb as *mut _ as u32); ipc.gpmem(1).write_value(0); trace!(" GPMEM[0]={:#X}, GPMEM[1]={}", cb as *mut _ as u32, 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); let power = pac::POWER_S; power .ltemodem() .startn() .write(|w| w.set_startn(pac::power::vals::Startn::START)); 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_control_len: 0, rx_data_len: 0, 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 (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); let state_ch = ch_runner.state_runner(); let control = Control { state: state_inner, state_ch, }; 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, Debug)] #[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, /// Number of entries in the control list rx_control_len: usize, /// Number of entries in the data list rx_data_len: usize, 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() }; trace!("modem control list length: {}", rx_control_len); trace!("modem data list length: {}", rx_data_len); self.rx_control_len = rx_control_len; self.rx_data_len = rx_data_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; let max = if is_control { self.rx_control_len } else { self.rx_data_len }; for i in 0..max { 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, state_ch: ch::StateRunner<'a>, } impl<'a> Control<'a> { /// Wait for modem IPC to be initialized. pub fn wait_init(&self) -> impl Future + '_ { poll_fn(|cx| { let mut state = self.state.borrow_mut(); if state.init { return Poll::Ready(()); } state.init_waker.register(cx.waker()); Poll::Pending }) } 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); } fn set_link_state(&self, state: ch::driver::LinkState) { self.state_ch.set_link_state(state); } } /// 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 = 32; #[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(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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()() } } } ================================================ FILE: embassy-net-ppp/CHANGELOG.md ================================================ # Changelog for embassy-net-ppp 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 - ReleaseDate ## 0.3.0 - 2026-03-11 - Update to embedded-io 0.7 - Update embassy-net-driver-channel to 0.4.0 - Update embassy-sync to 0.8.0 ## 0.2.1 - 2025-08-26 ## 0.2.0 - 2025-01-12 - Update `ppproto` to v0.2. - Use `core::net` IP types for IPv4 configuration instead of a custom type. ## 0.1.0 - 2024-01-12 First release. ================================================ FILE: embassy-net-ppp/Cargo.toml ================================================ [package] name = "embassy-net-ppp" version = "0.3.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 = "2024" 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 = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } embedded-io-async = { version = "0.7.0" } embassy-net-driver-channel = { version = "0.4.0", path = "../embassy-net-driver-channel" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } ppproto = { version = "0.2.1"} embassy-sync = { version = "0.8.0", 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"] ================================================ FILE: embassy-net-ppp/README.md ================================================ # `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). ================================================ FILE: embassy-net-ppp/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-net-ppp/src/lib.rs ================================================ #![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::{Either, select}; 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()() } } } ================================================ FILE: embassy-net-tuntap/Cargo.toml ================================================ [package] name = "embassy-net-tuntap" version = "0.1.1" 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 = "2024" 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 = "2.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" ================================================ FILE: embassy-net-tuntap/README.md ================================================ # `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. ================================================ FILE: embassy-net-tuntap/src/lib.rs ================================================ #![warn(missing_docs)] #![doc = include_str!("../README.md")] use std::io; use std::io::{Read, Write}; use std::os::fd::AsFd; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::prelude::BorrowedFd; use std::task::Context; use async_io::Async; use embassy_net_driver::{Capabilities, Driver, HardwareAddress, LinkState}; use log::*; 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 AsFd for TunTap { fn as_fd(&self) -> BorrowedFd<'_> { unsafe { BorrowedFd::borrow_raw(self.fd) } } } impl TunTap { /// Create a new TUN/TAP device. pub fn new(name: &str) -> io::Result { unsafe { let fd = libc::open(c"/dev/net/tun".as_ptr(), 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 = libc::IFF_TAP | libc::IFF_NO_PI; ifreq_ioctl(fd, &mut ifreq, libc::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, libc::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, hardware_address: [u8; 6], } impl TunTapDevice { /// Create a new TUN/TAP device. pub fn new(name: &str) -> io::Result { Ok(Self { device: Async::new(TunTap::new(name)?)?, hardware_address: [0x02, 0x03, 0x04, 0x05, 0x06, 0x07], }) } /// Sets the MAC address of the TAP device. /// /// Note that this can not be completely random; for example, choosing a multicast address /// (least significant bit of the first octet is 1) would cause smoltcp to crash. pub fn set_hardware_address(&mut self, hardware_address: [u8; 6]) { self.hardware_address = hardware_address; } } 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 unsafe { 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(self.hardware_address) } } #[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 unsafe { 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 } } ================================================ FILE: embassy-net-wiznet/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.3.0 - 2026-03-10 - Added experimental W6100 driver with disabled MAC filter (does not currently work with it enabled) - Added W6300 driver - Introduced `SOCKET_INTR_CLR` register which is needed on W6100 and later models (on W5100/W5500 this is shared with `SOCKET_INTR` and the address is the same) - Upgrade embassy-net-driver-channel to 0.4.0 ## 0.2.1 - 2025-08-26 ## 0.1.1 - 2025-08-14 - First release with changelog. ================================================ FILE: embassy-net-wiznet/Cargo.toml ================================================ [package] name = "embassy-net-wiznet" version = "0.3.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 = "2024" 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.4.0", path = "../embassy-net-driver-channel" } embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } defmt = { version = "1.0.1", optional = true } [features] defmt = ["dep:defmt", "embassy-net-driver-channel/defmt"] [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"] ================================================ FILE: embassy-net-wiznet/README.md ================================================ # 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 - W6100 - W6300 (Single SPI only) ## Interoperability This crate can run on any executor. It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). ================================================ FILE: embassy-net-wiznet/src/chip/mod.rs ================================================ //! Wiznet W5100s, W5500, W6100 and W6300 family driver. mod w5500; pub use w5500::W5500; mod w5100s; pub use w5100s::W5100S; mod w6100; pub use w6100::W6100; mod w6300; use embedded_hal_async::spi::SpiDevice; pub use w6300::W6300; 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; #[allow(dead_code)] const SOCKET_INTR: Self::Address; const SOCKET_INTR_CLR: 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 {} ================================================ FILE: embassy-net-wiznet/src/chip/w5100s.rs ================================================ 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_INTR_CLR: 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 } } ================================================ FILE: embassy-net-wiznet/src/chip/w5500.rs ================================================ 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_INTR_CLR: 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 } } ================================================ FILE: embassy-net-wiznet/src/chip/w6100.rs ================================================ use embedded_hal_async::spi::{Operation, SpiDevice}; #[repr(u8)] pub enum RegisterBlock { Common = 0x00, Socket0 = 0x01, TxBuf = 0x02, RxBuf = 0x03, } /// Wiznet W6100 chip. pub enum W6100 {} impl super::Chip for W6100 {} impl super::SealedChip for W6100 { type Address = (RegisterBlock, u16); const CHIP_VERSION: u8 = 0x46; const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x2004); const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x4120); // SIMR (SOCKET Interrupt Mask Register) const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x2114); const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x3000); const COMMON_VERSION: Self::Address = (RegisterBlock::Common, 0x0002); const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x0000); const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x0010); const SOCKET_RXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0220); const SOCKET_TXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0200); const SOCKET_TX_FREE_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0204); const SOCKET_TX_DATA_WRITE_PTR: Self::Address = (RegisterBlock::Socket0, 0x020C); const SOCKET_RECVD_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0224); const SOCKET_RX_DATA_READ_PTR: Self::Address = (RegisterBlock::Socket0, 0x0228); // Sn_IMR (SOCKET n Interrupt Mask Register) const SOCKET_INTR_MASK: Self::Address = (RegisterBlock::Socket0, 0x0024); // Sn_IR (SOCKET n Interrupt Register) const SOCKET_INTR: Self::Address = (RegisterBlock::Socket0, 0x0020); // Sn_IRCLR (Sn_IR Clear Register) const SOCKET_INTR_CLR: Self::Address = (RegisterBlock::Socket0, 0x0028); // MACRAW mode. See Page 57 of https://docs.wiznet.io/img/products/w6100/w6100_ds_v105e.pdf // Note: Bit 7 is MAC filter. On the W5500 this is normally turned ON however the W6100 will not successfully retrieve an IP address with this enabled. Disabling for now and will have live with the extra noise. const SOCKET_MODE_VALUE: u8 = 0b0000_0111; const BUF_SIZE: u16 = 0x1000; 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 } } ================================================ FILE: embassy-net-wiznet/src/chip/w6300.rs ================================================ use embedded_hal_async::spi::{Operation, SpiDevice}; #[repr(u8)] pub enum RegisterBlock { Common = 0x00, Socket0 = 0x01, TxBuf = 0x02, RxBuf = 0x03, } /// Wiznet W6300 chip. pub enum W6300 {} impl super::Chip for W6300 {} impl super::SealedChip for W6300 { type Address = (RegisterBlock, u16); // CIDR2 Minor Chip ID const CHIP_VERSION: u8 = 0x11; const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x2004); // SHAR0 (Source Hardware Address Register) const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x4120); // SIMR (SOCKET Interrupt Mask Register) const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x2114); // PHYSR (PHY Status Register) const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x3000); // CIDR2 (Minor Chip IP Register) const COMMON_VERSION: Self::Address = (RegisterBlock::Common, 0x0004); // Sn_MR (SOCKET n Mode Register) const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x0000); // Sn_CR (SOCKET n Command Register) const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x0010); // Sn_RX_BSR (SOCKET n RX Buffer Size Register) const SOCKET_RXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0220); // Sn_TX_BSR (SOCKET n TX Buffer Size Register) const SOCKET_TXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0200); // Sn_TX_FSR0 (SOCKET n TX Free Size Register) const SOCKET_TX_FREE_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0204); // Sn_TX_WR0 (SOCKET n TX Write Pointer Register) const SOCKET_TX_DATA_WRITE_PTR: Self::Address = (RegisterBlock::Socket0, 0x020C); // Sn_RX_RSR0 (SOCKET n RX Received Size Register) const SOCKET_RECVD_SIZE: Self::Address = (RegisterBlock::Socket0, 0x0224); // Sn_RX_RD0 (SOCKET n RX Read Pointer Register) const SOCKET_RX_DATA_READ_PTR: Self::Address = (RegisterBlock::Socket0, 0x0228); // Sn_IMR (SOCKET n Interrupt Mask Register) const SOCKET_INTR_MASK: Self::Address = (RegisterBlock::Socket0, 0x0024); // Sn_IR (SOCKET n Interrupt Register) const SOCKET_INTR: Self::Address = (RegisterBlock::Socket0, 0x0020); // Sn_IRCLR (Sn_IR Clear Register) const SOCKET_INTR_CLR: Self::Address = (RegisterBlock::Socket0, 0x0028); // MACRAW mode. See Page 57 of https://docs.wiznet.io/pdf-viewer?file=%2Fassets%2Ffiles%2F20251204_W6300_DS_V101E-4f4cd2e75de8d76f51a741f6a492ea01.pdf // Note: Bit 7 is MAC filter. On the W5500 this is normally turned ON however the W6300 will not successfully retrieve an IP address with this enabled. Disabling for now and will have live with the extra noise. const SOCKET_MODE_VALUE: u8 = 0b0000_0111; const BUF_SIZE: u16 = 0x1000; 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 instruction_phase = [address.0 as u8]; let address_phase = address.1.to_be_bytes(); let dummy_phase = [0u8]; let operations = &mut [ Operation::Write(&instruction_phase), Operation::Write(&address_phase), Operation::Write(&dummy_phase), Operation::TransferInPlace(data), ]; spi.transaction(operations).await } async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { // Set the Write Access Bit let instruction_phase = [(address.0 as u8) | 0b0010_0000]; let address_phase = address.1.to_be_bytes(); let dummy_phase = [0u8]; let operations = &mut [ Operation::Write(&instruction_phase), Operation::Write(&address_phase), Operation::Write(&dummy_phase), Operation::Write(data), ]; spi.transaction(operations).await } } ================================================ FILE: embassy-net-wiznet/src/device.rs ================================================ 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_CLR, &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 } } ================================================ FILE: embassy-net-wiznet/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] pub mod chip; mod device; use embassy_futures::select::{Either3, select3}; 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, }, )) } ================================================ FILE: embassy-nrf/CHANGELOG.md ================================================ # 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 - ReleaseDate ## 0.10.0 - 2026-03-10 - feat: implement CryptoCell RNG driver (nrf52840, nrf5340, nrf9120, nrf9160) - bugfix: avoid hang if calling `now()` before syscounter is enabled on nrf54 - bugfix: use correct pin count for the nrf54 chip family - bugfix: nrf54lm20 uses separate register for burst config - bugfix: enable burst for nrf54 if oversampling - bugfix: put SCL/SDA into high state during TWIM initialization - Update to embedded-io 0.7 - Update embassy-sync to 0.8.0 - Update embassy-embedded-hal to 0.6.0 - Update embassy-net-driver-channel to 0.4.0 ## 0.9.0 - 2025-12-15 - changed: apply trimming values from FICR.TRIMCNF on nrf53/54l - changed: do not panic on BufferedUarte overrun - added: allow direct access to the input pin of `gpiote::InputChannel` - bugfix: use DETECTMODE_SEC in GPIOTE in secure mode - added: allow configuring the idle state of GPIO pins connected to PWM channels - changed: allow configuring the PWM peripheral in the constructor of `SimplePwm` - changed: support setting duty cycles with inverted polarity in `SimplePwm` - added: support setting the duty cycles of all channels at once in `SimplePwm` - changed: updated to nrf-pac with nrf52/nrf53/nrf91 register layout more similar to nrf54 - added: support for nrf54l peripherals: uart, gpiote, twim, twis, spim, spis, dppi, pwm, saadc, cracen - added: support for changing nrf54l clock speed - bugfix: Do not write to UICR from non-secure code on nrf53 - bugfix: Add delay to uart init anomaly fix - changed: `BufferedUarte::read_ready` now uses the same definition for 'empty' so following read calls will not block when true is returned - added: add `gpiote::InputChannel::wait_for_high()` and `wait_for_low()` to wait for specific signal level - changed: `gpiote::InputChannel::wait()` now takes a mutable reference to `self` to avoid interference from concurrent calls - changed: `gpiote::InputChannel::wait()` now ensures events are seen as soon as the function is called, even if the future is not polled - bugfix: use correct flash size for nRF54l - changed: add workaround for anomaly 66 on nrf52 - added: expose PPI events available on SPIS peripheral - added: add basic GRTC time driver support for nRF54L * added: support for nrf54l10 and nrf54l05 * added: expose uicr write functions * added: support for nrf54lm20a - added: support buffered rram for nrf54 ## 0.8.0 - 2025-09-30 - changed: Remove `T: Instance` generic params in all drivers. - changed: nrf54l: Disable glitch detection and enable DC/DC in init. - changed: Add embassy-net-driver-channel implementation for IEEE 802.15.4 - changed: add persist() method for gpio and ppi - added: basic RTC driver - changed: add persist() method for gpio, gpiote, timer and ppi - changed: impl Drop for Timer - added: expose `regs` for timer driver - added: timer driver CC `clear_events` method - changed: Saadc reset in Drop impl, anomaly 241 - high power usage ## 0.7.0 - 2025-08-26 - bugfix: use correct analog input SAADC pins on nrf5340 ## 0.6.0 - 2025-08-04 - changed: update to latest embassy-time-queue-utils ## 0.5.0 - 2025-07-16 - changed: update to latest embassy-usb-driver ## 0.4.1 - 2025-07-14 - changed: nrf52833: configure internal LDO - changed: nrf5340: add more options to clock config - bugfix: clean the SAADC's register while dropping - changed: Remove Peripheral trait, rename PeripheralRef->Peri. - changed: take pins before interrupts in buffered uart init - changed: nrf5340: add wdt support - changed: remove nrf radio BLE - changed: add Blocking/Async Mode param. - bugfix: fix PWM loop count - bugfix: fixing the nrf54l drive configuration bug - changed: add temp driver for nrf5340 - changed: add support for rand 0.9 ## 0.3.1 - 2025-01-09 - bugfix: nrf twim return errors in async\_wait instead of waiting indefinitely - bugfix: fix missing setting input as disconnected. - changed: Modify Uarte and BufferedUarte initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) ## 0.3.0 - 2025-01-06 Firstly, this release switches embassy-nrf to chiptool-based `nrf-pac` implementations and lots of improvements, but also changes to API like peripheral and interrupt naming. Second big change is a refactoring of time driver contract with embassy-time-driver. From now on, the timer queue is handled by the time-driver implementation and `generic-queue` feature is provided by the `embassy-time-queue-utils` crate. Newly required dependencies are following: - embassy-time-0.4 - embassy-time-driver-0.2 - embassy-time-queue-utils-0.1 Add support for following NRF chips: - nRF54L15 (only gpio and timer support) Support for chip-specific features: - RESET operations for nrf5340 - POWER operations (system-off and wake-on-field) for nrf52840 and nrf9160 - nfc: - Adds support for NFC Tag emulator driver - pwm: - Fix incorrect pin assignments - Properly disconnect inputs when pins are set as output - uart: - `try_write` support for `BufferedUarte` - Support for `embedded_io_async` trait - spim: - Support SPIM4 peripheral on nrf5340-app - time: - Generic refactor of embassy-time-driver API - Fix for missed executor alarms in certain occasions (issue #3672, PR #3705). - twim: - Implement support for transactions - Remove support for consecutive Read operations due to hardware limitations ## 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 ================================================ FILE: embassy-nrf/Cargo.toml ================================================ [package] name = "embassy-nrf" version = "0.10.0" edition = "2024" 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] build = [ {target = "thumbv6m-none-eabi", features = ["gpiote", "nrf51", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52805", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52810", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52811", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52820", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52832", "reset-pin-as-gpio", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nfc-pins-as-gpio", "nrf52833", "time", "time-driver-rtc1"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf9160-s", "time", "time-driver-rtc1"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf9160-ns", "time", "time-driver-rtc1"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf5340-app-s", "time", "time-driver-rtc1"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf5340-app-ns", "time", "time-driver-rtc1"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf5340-net", "time", "time-driver-rtc1"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54l15-app-s", "time", "time-driver-grtc"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54l15-app-ns", "time", "time-driver-grtc"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54l10-app-s", "time", "time-driver-grtc"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54l10-app-ns", "time", "time-driver-grtc"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54l05-app-s", "time", "time-driver-grtc"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54l05-app-ns", "time", "time-driver-grtc"]}, {target = "thumbv8m.main-none-eabihf", features = ["gpiote", "nrf54lm20-app-s", "time", "time-driver-grtc"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52840", "time"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52840", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "nrf52840", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "log", "nrf52840", "time"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "log", "nrf52840", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["gpiote", "log", "nrf52840", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "gpiote", "nrf52840", "time"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "gpiote", "nrf52840", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "gpiote", "nrf52840", "time", "time-driver-rtc1"]}, {target = "thumbv7em-none-eabi", features = ["defmt", "gpiote", "nrf52840", "time", "time-driver-rtc1","qspi-multiwrite-flash"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "nrf51", "time", "time-driver-rtc1"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "nrf51", "time"]}, {target = "thumbv6m-none-eabi", features = ["nrf51", "time"]}, ] [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", "embassy-embedded-hal/time"] ## Enable defmt defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "embassy-usb-driver/defmt", "embassy-embedded-hal/defmt"] ## Enable log log = ["dep:log"] ## 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", "embassy-time-driver?/tick-hz-32_768"] ## Use GRTC (CC n=1, GRTC_1 irq) as the time driver for `embassy-time`, with a tick rate of 1 MHz time-driver-grtc = ["_time-driver", "embassy-time-driver?/tick-hz-1_000_000"] ## Enable embassy-net 802.15.4 driver net-driver = ["_net-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 = [] ## Allow using the LFXO pins as regular GPIO pins (P0_00/P0_01 on nRF53) lfxo-pins-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", "_spi-v1"] ## nRF52805 nrf52805 = ["nrf-pac/nrf52805", "_nrf52", "_spi-v1"] ## nRF52810 nrf52810 = ["nrf-pac/nrf52810", "_nrf52", "_spi-v1"] ## nRF52811 nrf52811 = ["nrf-pac/nrf52811", "_nrf52", "_spi-v1"] ## nRF52820 nrf52820 = ["nrf-pac/nrf52820", "_nrf52", "_spi-v1"] ## nRF52832 nrf52832 = ["nrf-pac/nrf52832", "_nrf52", "_nrf52832_anomaly_109", "_spi-v1"] ## 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", "_multi_wdt"] ## nRF54L15 application core in Non-Secure mode nrf54l15-app-ns = ["_nrf54l15-app", "_ns"] ## nRF54L10 application core in Secure mode nrf54l10-app-s = ["_nrf54l10-app", "_s", "_multi_wdt"] ## nRF54L10 application core in Non-Secure mode nrf54l10-app-ns = ["_nrf54l10-app", "_ns"] ## nRF54L05 application core in Secure mode nrf54l05-app-s = ["_nrf54l05-app", "_s", "_multi_wdt"] ## nRF54L05 application core in Non-Secure mode nrf54l05-app-ns = ["_nrf54l05-app", "_ns"] ## nRF54LM20 application core in Secure mode nrf54lm20-app-s = ["_nrf54lm20-app", "_s", "_multi_wdt"] ## 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", "_multi_wdt", "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"] _nrf54l10-app = ["_nrf54l10", "nrf-pac/nrf54l10-app"] _nrf54l10 = ["_nrf54l", "_gpio-p1", "_gpio-p2"] _nrf54l05-app = ["_nrf54l05", "nrf-pac/nrf54l05-app"] _nrf54l05 = ["_nrf54l", "_gpio-p1", "_gpio-p2"] _nrf54lm20-app = ["_nrf54lm20", "nrf-pac/nrf54lm20a-app"] _nrf54lm20 = ["_nrf54l", "_gpio-p1", "_gpio-p2"] _nrf54l = ["_dppi", "_grtc"] _nrf9160 = ["nrf-pac/nrf9160", "_dppi", "_spi-v1"] _nrf9120 = ["nrf-pac/nrf9120", "_dppi", "_spi-v1"] _nrf52 = ["_ppi"] _nrf51 = ["_ppi", "_spi-v1"] _nrf91 = [] _time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] _net-driver = ["dep:embassy-net-driver-channel"] # trustzone state. _s = [] _ns = [] _ppi = [] _dppi = [] _gpio-p1 = [] _gpio-p2 = [] _spi-v1 = [] _grtc = [] # Errata workarounds _nrf52832_anomaly_109 = [] # watchdog timer _multi_wdt = [] [dependencies] embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", optional = true } embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-utils", optional = true } embassy-time = { version = "0.5.1", path = "../embassy-time", optional = true } embassy-sync = { version = "0.8.0", path = "../embassy-sync" } embassy-hal-internal = { version = "0.5.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal", default-features = false } embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" } embassy-net-driver-channel = { version = "0.4.0", path = "../embassy-net-driver-channel", optional = true} embassy-futures = { version = "0.1.2", path = "../embassy-futures" } 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.7.1" } embedded-io-async = { version = "0.7.0" } rand-core-06 = { package = "rand_core", version = "0.6" } rand-core-09 = { package = "rand_core", version = "0.9" } # nrf-pac = { version = "0.2.0", git = "https://github.com/embassy-rs/nrf-pac.git", rev = "074935b9b24ab302fe812f91725937f57cd082cf" } nrf-pac = "0.3.0" defmt = { version = "1.0.1", optional = true } bitflags = "2.10.0" log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" critical-section = "1.1" 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" ================================================ FILE: embassy-nrf/README.md ================================================ # 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)) * nRF54 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf54l15)) * 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. ## Embassy-net-driver If the board supports IEEE 802.15.4 (see `src/radio/mod.rs`) the corresponding [embassy-net-driver](https://crates.io/crates/embassy-net-driver) implementation can be enabled with the feature `net-driver`. ## 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. ================================================ FILE: embassy-nrf/src/buffered_uarte/mod.rs ================================================ //! 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. #[cfg_attr(not(feature = "_nrf54l"), path = "v1.rs")] #[cfg_attr(feature = "_nrf54l", path = "v2.rs")] mod _version; pub use _version::*; ================================================ FILE: embassy-nrf/src/buffered_uarte/v1.rs ================================================ //! 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::{Future, poll_fn}; use core::marker::PhantomData; use core::slice; use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering, compiler_fence}; use core::task::Poll; use embassy_hal_internal::Peri; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; 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::InterruptExt; 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::{Config, Instance as UarteInstance, configure, configure_rx_pins, configure_tx_pins, drop_tx_rx}; use crate::{EASY_DMA_SIZE, interrupt, pac}; 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, rx_overrun: AtomicBool, } /// UART error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { /// Buffer Overrun Overrun, } 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), rx_overrun: AtomicBool::new(false), } } } /// 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() { s.rx_overrun.store(true, Ordering::Release); ss.rx_waker.wake(); } } // 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_dma().rx().end().read() != 0 { //trace!(" irq_rx: endrx"); r.events_dma().rx().end().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_dma().rx().ready().read() != 0 || !s.rx_started.load(Ordering::Relaxed) { //trace!(" irq_rx: rxstarted"); let (ptr, len) = rx.push_buf(); if len >= half_len { r.events_dma().rx().ready().write_value(0); //trace!(" irq_rx: starting second {:?}", half_len); // Set up the DMA read r.dma().rx().ptr().write_value(ptr as u32); r.dma().rx().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_dma().rx().end().read() != 0 { //trace!(" irq_rx: endrx"); r.events_dma().rx().end().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_dma().rx().start().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_dmarxready(true)); } } } // ============================= if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { // TX end if r.events_dma().tx().end().read() != 0 { r.events_dma().tx().end().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.dma().tx().ptr().write_value(ptr as u32); r.dma().tx().maxcnt().write(|w| w.set_maxcnt(len as _)); // Start UARTE Transmit transaction r.tasks_dma().tx().start().write_value(1); } } } //trace!("irq: end"); } } /// Buffered UARTE driver. pub struct BufferedUarte<'d> { tx: BufferedUarteTx<'d>, rx: BufferedUarteRx<'d>, } impl<'d> Unpin for BufferedUarte<'d> {} impl<'d> BufferedUarte<'d> { /// 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: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, impl ConfigurableChannel>, ppi_ch2: Peri<'d, impl ConfigurableChannel>, ppi_group: Peri<'d, impl Group>, rxd: Peri<'d, impl GpioPin>, txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner( uarte, timer, ppi_ch1.into(), ppi_ch2.into(), ppi_group.into(), rxd.into(), txd.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: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, impl ConfigurableChannel>, ppi_ch2: Peri<'d, impl ConfigurableChannel>, ppi_group: Peri<'d, impl Group>, rxd: Peri<'d, impl GpioPin>, txd: Peri<'d, impl GpioPin>, cts: Peri<'d, impl GpioPin>, rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner( uarte, timer, ppi_ch1.into(), ppi_ch2.into(), ppi_group.into(), rxd.into(), txd.into(), Some(cts.into()), Some(rts.into()), config, rx_buffer, tx_buffer, ) } #[allow(clippy::too_many_arguments)] fn new_inner( peri: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, AnyConfigurableChannel>, ppi_ch2: Peri<'d, AnyConfigurableChannel>, ppi_group: Peri<'d, AnyGroup>, rxd: Peri<'d, AnyPin>, txd: Peri<'d, AnyPin>, cts: Option>, rts: Option>, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { let r = U::regs(); let irq = U::Interrupt::IRQ; let state = U::state(); configure(r, 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); r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); irq.pend(); unsafe { irq.enable() }; 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) { self.tx.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>, BufferedUarteTx<'d>) { (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>, &mut BufferedUarteTx<'d>) { (&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> { r: pac::uarte::Uarte, _irq: interrupt::Interrupt, state: &'static crate::uarte::State, buffered_state: &'static State, _p: PhantomData<&'d ()>, } impl<'d> BufferedUarteTx<'d> { /// Create a new BufferedUarteTx without hardware flow control. pub fn new( uarte: Peri<'d, U>, txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, txd.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: Peri<'d, U>, txd: Peri<'d, impl GpioPin>, cts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, txd.into(), Some(cts.into()), config, tx_buffer) } fn new_inner( peri: Peri<'d, U>, txd: Peri<'d, AnyPin>, cts: Option>, config: Config, tx_buffer: &'d mut [u8], ) -> Self { let r = U::regs(); let irq = U::Interrupt::IRQ; let state = U::state(); let _buffered_state = U::buffered_state(); configure(r, config, cts.is_some()); let this = Self::new_innerer(peri, txd, cts, tx_buffer); r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); irq.pend(); unsafe { irq.enable() }; state.tx_rx_refcount.store(1, Ordering::Relaxed); this } fn new_innerer( _peri: Peri<'d, U>, txd: Peri<'d, AnyPin>, cts: Option>, tx_buffer: &'d mut [u8], ) -> Self { let r = U::regs(); let irq = U::Interrupt::IRQ; let state = U::state(); let buffered_state = U::buffered_state(); configure_tx_pins(r, txd, cts); // Initialize state buffered_state.tx_count.store(0, Ordering::Relaxed); let len = tx_buffer.len(); unsafe { buffered_state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; r.events_dma().tx().ready().write_value(0); // Enable interrupts r.intenset().write(|w| { w.set_dmatxend(true); }); Self { r, _irq: irq, state, buffered_state, _p: PhantomData, } } /// Write a buffer into this writer, returning how many bytes were written. pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a + use<'a, 'd> { poll_fn(move |cx| { //trace!("poll_write: {:?}", buf.len()); let ss = self.state; let s = self.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); self._irq.pend(); Poll::Ready(Ok(n)) }) } /// 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 = self.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); self._irq.pend(); Ok(n) } /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. pub fn flush(&mut self) -> impl Future> + '_ { let ss = self.state; let s = self.buffered_state; poll_fn(move |cx| { //trace!("poll_flush"); if !s.tx_buf.is_empty() { //trace!("poll_flush: pending"); ss.tx_waker.register(cx.waker()); return Poll::Pending; } Poll::Ready(Ok(())) }) } /// Adjust the baud rate to the provided value. pub fn set_baudrate(&mut self, baudrate: Baudrate) { self.r.baudrate().write(|w| w.set_baudrate(baudrate)); } } impl<'a> Drop for BufferedUarteTx<'a> { fn drop(&mut self) { let r = self.r; r.intenclr().write(|w| { w.set_txdrdy(true); w.set_dmatxready(true); w.set_txstopped(true); }); r.events_txstopped().write_value(0); r.tasks_dma().tx().stop().write_value(1); while r.events_txstopped().read() == 0 {} let s = self.buffered_state; unsafe { s.tx_buf.deinit() } let s = self.state; drop_tx_rx(r, s); } } /// Reader part of the buffered UARTE driver. pub struct BufferedUarteRx<'d> { r: pac::uarte::Uarte, state: &'static crate::uarte::State, buffered_state: &'static State, timer: Timer<'d>, _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, _ppi_group: PpiGroup<'d, AnyGroup>, _p: PhantomData<&'d ()>, } impl<'d> BufferedUarteRx<'d> { /// 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: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, impl ConfigurableChannel>, ppi_ch2: Peri<'d, impl ConfigurableChannel>, ppi_group: Peri<'d, impl Group>, _irq: impl interrupt::typelevel::Binding> + 'd, rxd: Peri<'d, impl GpioPin>, config: Config, rx_buffer: &'d mut [u8], ) -> Self { Self::new_inner( uarte, timer, ppi_ch1.into(), ppi_ch2.into(), ppi_group.into(), rxd.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: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, impl ConfigurableChannel>, ppi_ch2: Peri<'d, impl ConfigurableChannel>, ppi_group: Peri<'d, impl Group>, rxd: Peri<'d, impl GpioPin>, rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, rx_buffer: &'d mut [u8], ) -> Self { Self::new_inner( uarte, timer, ppi_ch1.into(), ppi_ch2.into(), ppi_group.into(), rxd.into(), Some(rts.into()), config, rx_buffer, ) } #[allow(clippy::too_many_arguments)] fn new_inner( peri: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, AnyConfigurableChannel>, ppi_ch2: Peri<'d, AnyConfigurableChannel>, ppi_group: Peri<'d, AnyGroup>, rxd: Peri<'d, AnyPin>, rts: Option>, config: Config, rx_buffer: &'d mut [u8], ) -> Self { let r = U::regs(); let irq = U::Interrupt::IRQ; let state = U::state(); let _buffered_state = U::buffered_state(); configure(r, config, rts.is_some()); let this = Self::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); irq.pend(); unsafe { irq.enable() }; state.tx_rx_refcount.store(1, Ordering::Relaxed); this } #[allow(clippy::too_many_arguments)] fn new_innerer( _peri: Peri<'d, U>, timer: Peri<'d, T>, ppi_ch1: Peri<'d, AnyConfigurableChannel>, ppi_ch2: Peri<'d, AnyConfigurableChannel>, ppi_group: Peri<'d, AnyGroup>, rxd: Peri<'d, AnyPin>, rts: Option>, rx_buffer: &'d mut [u8], ) -> Self { assert!(rx_buffer.len() % 2 == 0); let r = U::regs(); let state = U::state(); let buffered_state = U::buffered_state(); configure_rx_pins(r, rxd, rts); // Initialize state buffered_state.rx_started_count.store(0, Ordering::Relaxed); buffered_state.rx_ended_count.store(0, Ordering::Relaxed); buffered_state.rx_started.store(false, Ordering::Relaxed); buffered_state.rx_overrun.store(false, Ordering::Relaxed); let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); unsafe { buffered_state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_len) }; // clear errors let errors = r.errorsrc().read(); r.errorsrc().write_value(errors); r.events_dma().rx().ready().write_value(0); r.events_error().write_value(0); r.events_dma().rx().end().write_value(0); // Enable interrupts r.intenset().write(|w| { w.set_dmatxend(true); w.set_dmarxready(true); w.set_error(true); w.set_dmarxend(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(); buffered_state .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_dma().rx().end()), Task::from_reg(r.tasks_dma().rx().start()), ppi_group.task_disable_all(), ); ppi_ch2.disable(); ppi_group.add_channel(&ppi_ch2); Self { r, state, buffered_state, timer, _ppi_ch1: ppi_ch1, _ppi_ch2: ppi_ch2, _ppi_group: ppi_group, _p: PhantomData, } } fn get_rxdrdy_counter(&self) -> usize { let s = self.buffered_state; let timer = &self.timer; // Read the RXDRDY counter. timer.cc(0).capture(); let mut rxdrdy = timer.cc(0).read() as usize; //trace!(" rxdrdy count = {:?}", rxdrdy); // 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 rxdrdy > s.rx_buf.len() * 2 { rxdrdy = 0; } rxdrdy } /// 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 fn fill_buf(&mut self) -> impl Future> { let r = self.r; let s = self.buffered_state; let ss = self.state; poll_fn(move |cx| { compiler_fence(Ordering::SeqCst); //trace!("poll_read"); if s.rx_overrun.swap(false, Ordering::Acquire) { return Poll::Ready(Err(Error::Overrun)); } let mut end = self.get_rxdrdy_counter(); // 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) })) }) } /// 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 = self.buffered_state; let mut rx = unsafe { s.rx_buf.reader() }; rx.pop_done(amt); self.r.intenset().write(|w| w.set_dmarxready(true)); } /// we are ready to read if there is data in the buffer fn read_ready(&self) -> Result { let state = self.buffered_state; if state.rx_overrun.swap(false, Ordering::Acquire) { return Err(Error::Overrun); } let start = state.rx_buf.start.load(Ordering::Relaxed); let end = self.get_rxdrdy_counter(); Ok(start != end) } } impl<'a> Drop for BufferedUarteRx<'a> { fn drop(&mut self) { self._ppi_group.disable_all(); let r = self.r; self.timer.stop(); r.intenclr().write(|w| { w.set_rxdrdy(true); w.set_dmarxready(true); w.set_rxto(true); }); r.events_rxto().write_value(0); r.tasks_dma().rx().stop().write_value(1); while r.events_rxto().read() == 0 {} let s = self.buffered_state; unsafe { s.rx_buf.deinit() } let s = self.state; drop_tx_rx(r, s); } } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self { Error::Overrun => write!(f, "Buffer Overrun"), } } } impl core::error::Error for Error {} mod _embedded_io { use super::*; impl embedded_io_async::Error for Error { fn kind(&self) -> embedded_io_async::ErrorKind { match *self { Error::Overrun => embedded_io_async::ErrorKind::OutOfMemory, } } } impl<'d> embedded_io_async::ErrorType for BufferedUarte<'d> { type Error = Error; } impl<'d> embedded_io_async::ErrorType for BufferedUarteRx<'d> { type Error = Error; } impl<'d> embedded_io_async::ErrorType for BufferedUarteTx<'d> { type Error = Error; } impl<'d> embedded_io_async::Read for BufferedUarte<'d> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).await } } impl<'d> embedded_io_async::Read for BufferedUarteRx<'d> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).await } } impl<'d> embedded_io_async::ReadReady for BufferedUarte<'d> { fn read_ready(&mut self) -> Result { self.rx.read_ready() } } impl<'d> embedded_io_async::ReadReady for BufferedUarteRx<'d> { fn read_ready(&mut self) -> Result { let state = self.buffered_state; Ok(!state.rx_buf.is_empty()) } } impl<'d> embedded_io_async::BufRead for BufferedUarte<'d> { 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> embedded_io_async::BufRead for BufferedUarteRx<'d> { 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> embedded_io_async::Write for BufferedUarte<'d> { 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> embedded_io_async::Write for BufferedUarteTx<'d> { async fn write(&mut self, buf: &[u8]) -> Result { self.write(buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { self.flush().await } } } ================================================ FILE: embassy-nrf/src/buffered_uarte/v2.rs ================================================ //! 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. //! //! The code is based on the generic buffered_uarte implementation but uses the nrf54l //! frame timeout event to correctly determine the size of transferred data. //! Counting of rxrdy events, used in the generic implementation, cannot be applied //! to nrf54l chips, as they buffer up to 4 bytes in a single DMA transaction. //! The only reliable way to find the number of bytes received is to stop the transfer, //! wait for the DMA stopped event, and read the value in the rx.dma.amount register. //! This also flushes all in-flight data to RAM. use core::cmp::min; use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::slice; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering, compiler_fence}; use core::task::Poll; use embassy_hal_internal::Peri; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; 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::uarte::{Config, Instance as UarteInstance, configure, configure_rx_pins, configure_tx_pins, drop_tx_rx}; use crate::{EASY_DMA_SIZE, interrupt, pac}; pub(crate) struct State { tx_buf: RingBuffer, tx_count: AtomicUsize, rx_buf: RingBuffer, rx_started: AtomicBool, } /// 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 core::fmt::Display for Error { fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self {} } } impl core::error::Error for Error {} 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), } } } /// Interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { info!("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 UART overrun"); } } let first_run = !s.rx_started.swap(true, Ordering::Relaxed); if r.events_dma().rx().end().read() != 0 || first_run { //trace!(" irq_rx: endrx"); r.events_dma().rx().end().write_value(0); if !first_run { // Received some bytes, wake task. let rxed = r.dma().rx().amount().read().amount() as usize; rx.push_done(rxed); ss.rx_waker.wake(); } let (ptr, len) = rx.push_buf(); if len == 0 { panic!("BufferedUarte buffer overrun"); } let len = if len > half_len { half_len } else { len }; // Set up the DMA read r.dma().rx().ptr().write_value(ptr as u32); r.dma().rx().maxcnt().write(|w| w.set_maxcnt(len as _)); // manually start r.tasks_dma().rx().start().write_value(1); } } // ============================= if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { // TX end if r.events_dma().tx().end().read() != 0 { r.events_dma().tx().end().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.dma().tx().ptr().write_value(ptr as u32); r.dma().tx().maxcnt().write(|w| w.set_maxcnt(len as _)); // Start UARTE Transmit transaction r.tasks_dma().tx().start().write_value(1); } } } //trace!("irq: end"); } } /// Buffered UARTE driver. pub struct BufferedUarte<'d, U: UarteInstance> { tx: BufferedUarteTx<'d, U>, rx: BufferedUarteRx<'d, U>, } impl<'d, U: UarteInstance> Unpin for BufferedUarte<'d, U> {} impl<'d, U: UarteInstance> BufferedUarte<'d, U> { /// Create a new BufferedUarte without hardware flow control. #[allow(clippy::too_many_arguments)] pub fn new( uarte: Peri<'d, U>, rxd: Peri<'d, impl GpioPin>, txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, rxd.into(), txd.into(), None, None, config, rx_buffer, tx_buffer) } /// Create a new BufferedUarte with hardware flow control (RTS/CTS) #[allow(clippy::too_many_arguments)] pub fn new_with_rtscts( uarte: Peri<'d, U>, rxd: Peri<'d, impl GpioPin>, txd: Peri<'d, impl GpioPin>, cts: Peri<'d, impl GpioPin>, rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner( uarte, rxd.into(), txd.into(), Some(cts.into()), Some(rts.into()), config, rx_buffer, tx_buffer, ) } #[allow(clippy::too_many_arguments)] fn new_inner( peri: Peri<'d, U>, rxd: Peri<'d, AnyPin>, txd: Peri<'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, 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>, 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>, &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: Peri<'d, U>, } impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { /// Create a new BufferedUarteTx without hardware flow control. pub fn new( uarte: Peri<'d, U>, txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, txd.into(), None, config, tx_buffer) } /// Create a new BufferedUarte with hardware flow control (RTS/CTS) pub fn new_with_cts( uarte: Peri<'d, U>, txd: Peri<'d, impl GpioPin>, cts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, tx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, txd.into(), Some(cts.into()), config, tx_buffer) } fn new_inner( peri: Peri<'d, U>, txd: Peri<'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: Peri<'d, U>, txd: Peri<'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_dma().tx().ready().write_value(0); // Enable interrupts r.intenset().write(|w| { w.set_dmatxend(true); }); Self { _peri: peri } } /// Write a buffer into this writer, returning how many bytes were written. pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a { 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)) }) } /// 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 fn flush(&mut self) -> impl Future> + '_ { 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(())) }) } } 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_dmatxready(true); w.set_txstopped(true); }); r.events_txstopped().write_value(0); r.tasks_dma().tx().stop().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> { _peri: Peri<'d, U>, } impl<'d, U: UarteInstance> BufferedUarteRx<'d, U> { /// Create a new BufferedUarte without hardware flow control. #[allow(clippy::too_many_arguments)] pub fn new( uarte: Peri<'d, U>, _irq: impl interrupt::typelevel::Binding> + 'd, rxd: Peri<'d, impl GpioPin>, config: Config, rx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, rxd.into(), None, config, rx_buffer) } /// Create a new BufferedUarte with hardware flow control (RTS/CTS) #[allow(clippy::too_many_arguments)] pub fn new_with_rts( uarte: Peri<'d, U>, rxd: Peri<'d, impl GpioPin>, rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, rx_buffer: &'d mut [u8], ) -> Self { Self::new_inner(uarte, rxd.into(), Some(rts.into()), config, rx_buffer) } #[allow(clippy::too_many_arguments)] fn new_inner( peri: Peri<'d, U>, rxd: Peri<'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, 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: Peri<'d, U>, rxd: Peri<'d, AnyPin>, rts: Option>, rx_buffer: &'d mut [u8], ) -> Self { let r = U::regs(); configure_rx_pins(r, rxd, rts); // Initialize state let s = U::buffered_state(); let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); let rx_ptr = rx_buffer.as_mut_ptr(); unsafe { s.rx_buf.init(rx_ptr, rx_len) }; // clear errors let errors = r.errorsrc().read(); r.errorsrc().write_value(errors); r.events_error().write_value(0); r.events_dma().rx().end().write_value(0); // set timeout-to-stop short r.shorts().write(|w| { w.set_frametimeout_dma_rx_stop(true); }); // set default timeout r.frametimeout().write_value(pac::uarte::regs::Frametimeout(0x10)); // Enable interrupts r.intenset().write(|w| { w.set_dmatxend(true); w.set_error(true); w.set_dmarxend(true); }); Self { _peri: peri } } /// 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 fn fill_buf(&mut self) -> impl Future> { poll_fn(move |cx| { compiler_fence(Ordering::SeqCst); //trace!("poll_read"); let s = U::buffered_state(); let ss = U::state(); let mut rx = unsafe { s.rx_buf.reader() }; let (ptr, n) = rx.pop_buf(); if n == 0 { //trace!(" empty"); ss.rx_waker.register(cx.waker()); Poll::Pending } else { Poll::Ready(Ok(unsafe { slice::from_raw_parts(ptr, n) })) } }) } /// 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); } /// 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> Drop for BufferedUarteRx<'a, U> { fn drop(&mut self) { let r = U::regs(); r.intenclr().write(|w| { w.set_rxto(true); }); r.events_rxto().write_value(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> embedded_io_async::ErrorType for BufferedUarte<'d, U> { type Error = Error; } impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarteRx<'d, U> { type Error = Error; } impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarteTx<'d, U> { type Error = Error; } impl<'d, U: UarteInstance> embedded_io_async::Read for BufferedUarte<'d, U> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).await } } impl<'d: 'd, U: UarteInstance> embedded_io_async::Read for BufferedUarteRx<'d, U> { async fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).await } } impl<'d, U: UarteInstance> embedded_io_async::ReadReady for BufferedUarte<'d, U> { fn read_ready(&mut self) -> Result { BufferedUarteRx::<'d, U>::read_ready() } } impl<'d, U: UarteInstance> embedded_io_async::ReadReady for BufferedUarteRx<'d, U> { fn read_ready(&mut self) -> Result { Self::read_ready() } } impl<'d, U: UarteInstance> embedded_io_async::BufRead for BufferedUarte<'d, U> { 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> embedded_io_async::BufRead for BufferedUarteRx<'d, U> { 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> embedded_io_async::Write for BufferedUarte<'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 } } 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 } } } ================================================ FILE: embassy-nrf/src/chips/nrf51.rs ================================================ 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, #[cfg(not(feature = "time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf52805.rs ================================================ 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, #[cfg(not(feature="time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( CLOCK_POWER, RADIO, UARTE0, TWI0, SPI0, GPIOTE, SAADC, TIMER0, TIMER1, TIMER2, TEMP, RNG, ECB, AAR_CCM, WDT, RTC0, RTC1, QDEC, EGU0_SWI0, EGU1_SWI1, SWI2, SWI3, SWI4, SWI5, ); ================================================ FILE: embassy-nrf/src/chips/nrf52810.rs ================================================ 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, #[cfg(not(feature="time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf52811.rs ================================================ 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, #[cfg(not(feature="time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf52820.rs ================================================ 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, #[cfg(not(feature="time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf52832.rs ================================================ 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, #[cfg(not(feature="time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); impl_rtc!(RTC2, RTC2, RTC2); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf52833.rs ================================================ 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, #[cfg(not(feature = "time-driver-rtc1"))] 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); impl_rtc!(RTC2, RTC2, RTC2); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf52840.rs ================================================ 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, #[cfg(not(feature = "time-driver-rtc1"))] 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, // CryptoCell RNG CC_RNG } 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_ccrng!(CC_RNG, CC_RNG, CRYPTOCELL); 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); impl_rtc!(RTC2, RTC2, RTC2); 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, PPI, 0 => configurable); impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); impl_ppi_channel!(PPI_CH20, PPI, 20 => static); impl_ppi_channel!(PPI_CH21, PPI, 21 => static); impl_ppi_channel!(PPI_CH22, PPI, 22 => static); impl_ppi_channel!(PPI_CH23, PPI, 23 => static); impl_ppi_channel!(PPI_CH24, PPI, 24 => static); impl_ppi_channel!(PPI_CH25, PPI, 25 => static); impl_ppi_channel!(PPI_CH26, PPI, 26 => static); impl_ppi_channel!(PPI_CH27, PPI, 27 => static); impl_ppi_channel!(PPI_CH28, PPI, 28 => static); impl_ppi_channel!(PPI_CH29, PPI, 29 => static); impl_ppi_channel!(PPI_CH30, PPI, 30 => static); impl_ppi_channel!(PPI_CH31, PPI, 31 => static); impl_ppi_group!(PPI_GROUP0, PPI, 0); impl_ppi_group!(PPI_GROUP1, PPI, 1); impl_ppi_group!(PPI_GROUP2, PPI, 2); impl_ppi_group!(PPI_GROUP3, PPI, 3); impl_ppi_group!(PPI_GROUP4, PPI, 4); impl_ppi_group!(PPI_GROUP5, PPI, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf5340_app.rs ================================================ /// 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, CC_HOST_RGF_S as CC_HOST_RGF, CC_RNG_S as CC_RNG, 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, #[cfg(not(feature = "time-driver-rtc1"))] RTC1, // WDT WDT0, WDT1, // 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, // IPC IPC, // GPIO port 0 #[cfg(feature = "lfxo-pins-as-gpio")] P0_00, #[cfg(feature = "lfxo-pins-as-gpio")] 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, // CryptoCell RNG #[cfg(feature = "_s")] CC_RNG } impl_ipc!(IPC, IPC, IPC); 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); #[cfg(feature = "_s")] impl_ccrng!(CC_RNG, CC_RNG, CRYPTOCELL); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); impl_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); impl_qspi!(QSPI, QSPI, QSPI); impl_pdm!(PDM0, PDM0, PDM0); impl_qdec!(QDEC0, QDEC0, QDEC0); impl_qdec!(QDEC1, QDEC1, QDEC1); #[cfg(feature = "lfxo-pins-as-gpio")] impl_pin!(P0_00, 0, 0); #[cfg(feature = "lfxo-pins-as-gpio")] 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, DPPIC, 0 => configurable); impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); impl_ppi_channel!(PPI_CH16, DPPIC, 16 => configurable); impl_ppi_channel!(PPI_CH17, DPPIC, 17 => configurable); impl_ppi_channel!(PPI_CH18, DPPIC, 18 => configurable); impl_ppi_channel!(PPI_CH19, DPPIC, 19 => configurable); impl_ppi_channel!(PPI_CH20, DPPIC, 20 => configurable); impl_ppi_channel!(PPI_CH21, DPPIC, 21 => configurable); impl_ppi_channel!(PPI_CH22, DPPIC, 22 => configurable); impl_ppi_channel!(PPI_CH23, DPPIC, 23 => configurable); impl_ppi_channel!(PPI_CH24, DPPIC, 24 => configurable); impl_ppi_channel!(PPI_CH25, DPPIC, 25 => configurable); impl_ppi_channel!(PPI_CH26, DPPIC, 26 => configurable); impl_ppi_channel!(PPI_CH27, DPPIC, 27 => configurable); impl_ppi_channel!(PPI_CH28, DPPIC, 28 => configurable); impl_ppi_channel!(PPI_CH29, DPPIC, 29 => configurable); impl_ppi_channel!(PPI_CH30, DPPIC, 30 => configurable); impl_ppi_channel!(PPI_CH31, DPPIC, 31 => configurable); impl_ppi_group!(PPI_GROUP0, DPPIC, 0); impl_ppi_group!(PPI_GROUP1, DPPIC, 1); impl_ppi_group!(PPI_GROUP2, DPPIC, 2); impl_ppi_group!(PPI_GROUP3, DPPIC, 3); impl_ppi_group!(PPI_GROUP4, DPPIC, 4); impl_ppi_group!(PPI_GROUP5, DPPIC, 5); impl_saadc_input!(P0_04, ANALOG_INPUT0); impl_saadc_input!(P0_05, ANALOG_INPUT1); impl_saadc_input!(P0_06, ANALOG_INPUT2); impl_saadc_input!(P0_07, ANALOG_INPUT3); impl_saadc_input!(P0_25, ANALOG_INPUT4); impl_saadc_input!(P0_26, ANALOG_INPUT5); impl_saadc_input!(P0_27, ANALOG_INPUT6); impl_saadc_input!(P0_28, 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); impl_wdt!(WDT0, WDT0, WDT0, 0); impl_wdt!(WDT1, WDT1, WDT1, 1); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf5340_net.rs ================================================ /// 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, #[cfg(not(feature = "time-driver-rtc1"))] 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, // IPC IPC, // 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, // TEMP TEMP, } impl_ipc!(IPC, IPC, IPC); 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_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, DPPIC, 0 => configurable); impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); impl_ppi_channel!(PPI_CH16, DPPIC, 16 => configurable); impl_ppi_channel!(PPI_CH17, DPPIC, 17 => configurable); impl_ppi_channel!(PPI_CH18, DPPIC, 18 => configurable); impl_ppi_channel!(PPI_CH19, DPPIC, 19 => configurable); impl_ppi_channel!(PPI_CH20, DPPIC, 20 => configurable); impl_ppi_channel!(PPI_CH21, DPPIC, 21 => configurable); impl_ppi_channel!(PPI_CH22, DPPIC, 22 => configurable); impl_ppi_channel!(PPI_CH23, DPPIC, 23 => configurable); impl_ppi_channel!(PPI_CH24, DPPIC, 24 => configurable); impl_ppi_channel!(PPI_CH25, DPPIC, 25 => configurable); impl_ppi_channel!(PPI_CH26, DPPIC, 26 => configurable); impl_ppi_channel!(PPI_CH27, DPPIC, 27 => configurable); impl_ppi_channel!(PPI_CH28, DPPIC, 28 => configurable); impl_ppi_channel!(PPI_CH29, DPPIC, 29 => configurable); impl_ppi_channel!(PPI_CH30, DPPIC, 30 => configurable); impl_ppi_channel!(PPI_CH31, DPPIC, 31 => configurable); impl_ppi_group!(PPI_GROUP0, DPPIC, 0); impl_ppi_group!(PPI_GROUP1, DPPIC, 1); impl_ppi_group!(PPI_GROUP2, DPPIC, 2); impl_ppi_group!(PPI_GROUP3, DPPIC, 3); impl_ppi_group!(PPI_GROUP4, DPPIC, 4); impl_ppi_group!(PPI_GROUP5, DPPIC, 5); impl_radio!(RADIO, RADIO, RADIO); impl_egu!(EGU0, EGU0, EGU0); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf54l05_app.rs ================================================ /// 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, 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, 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::{ FICR_NS as FICR, 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, 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, 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; // 1.5 MB NVM #[allow(unused)] pub const FLASH_SIZE: usize = 1524 * 1024; embassy_hal_internal::peripherals! { // PPI PPI00_CH0, PPI00_CH1, PPI00_CH2, PPI00_CH3, PPI00_CH4, PPI00_CH5, PPI00_CH6, PPI00_CH7, PPI10_CH0, PPI10_CH1, PPI10_CH2, PPI10_CH3, PPI10_CH4, PPI10_CH5, PPI10_CH6, PPI10_CH7, PPI10_CH8, PPI10_CH9, PPI10_CH10, PPI10_CH11, PPI10_CH12, PPI10_CH13, PPI10_CH14, PPI10_CH15, PPI10_CH16, PPI10_CH17, PPI10_CH18, PPI10_CH19, PPI10_CH20, PPI10_CH21, PPI10_CH22, PPI10_CH23, PPI20_CH0, PPI20_CH1, PPI20_CH2, PPI20_CH3, PPI20_CH4, PPI20_CH5, PPI20_CH6, PPI20_CH7, PPI20_CH8, PPI20_CH9, PPI20_CH10, PPI20_CH11, PPI20_CH12, PPI20_CH13, PPI20_CH14, PPI20_CH15, PPI30_CH0, PPI30_CH1, PPI30_CH2, PPI30_CH3, PPI00_GROUP0, PPI00_GROUP1, PPI10_GROUP0, PPI10_GROUP1, PPI10_GROUP2, PPI10_GROUP3, PPI10_GROUP4, PPI10_GROUP5, PPI20_GROUP0, PPI20_GROUP1, PPI20_GROUP2, PPI20_GROUP3, PPI20_GROUP4, PPI20_GROUP5, PPI30_GROUP0, PPI30_GROUP1, // PPI BRIDGE channels PPIB00_CH0, PPIB00_CH1, PPIB00_CH2, PPIB00_CH3, PPIB00_CH4, PPIB00_CH5, PPIB00_CH6, PPIB00_CH7, PPIB01_CH0, PPIB01_CH1, PPIB01_CH2, PPIB01_CH3, PPIB01_CH4, PPIB01_CH5, PPIB01_CH6, PPIB01_CH7, PPIB10_CH0, PPIB10_CH1, PPIB10_CH2, PPIB10_CH3, PPIB10_CH4, PPIB10_CH5, PPIB10_CH6, PPIB10_CH7, PPIB11_CH0, PPIB11_CH1, PPIB11_CH2, PPIB11_CH3, PPIB11_CH4, PPIB11_CH5, PPIB11_CH6, PPIB11_CH7, PPIB11_CH8, PPIB11_CH9, PPIB11_CH10, PPIB11_CH11, PPIB11_CH12, PPIB11_CH13, PPIB11_CH14, PPIB11_CH15, PPIB20_CH0, PPIB20_CH1, PPIB20_CH2, PPIB20_CH3, PPIB20_CH4, PPIB20_CH5, PPIB20_CH6, PPIB20_CH7, PPIB21_CH0, PPIB21_CH1, PPIB21_CH2, PPIB21_CH3, PPIB21_CH4, PPIB21_CH5, PPIB21_CH6, PPIB21_CH7, PPIB21_CH8, PPIB21_CH9, PPIB21_CH10, PPIB21_CH11, PPIB21_CH12, PPIB21_CH13, PPIB21_CH14, PPIB21_CH15, PPIB22_CH0, PPIB22_CH1, PPIB22_CH2, PPIB22_CH3, PPIB30_CH0, PPIB30_CH1, PPIB30_CH2, PPIB30_CH3, // Timers TIMER00, TIMER10, TIMER20, TIMER21, TIMER22, TIMER23, TIMER24, // 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, // GRTC GRTC_CH0, #[cfg(not(feature = "time-driver-grtc"))] GRTC_CH1, GRTC_CH2, GRTC_CH3, GRTC_CH4, GRTC_CH5, GRTC_CH6, GRTC_CH7, GRTC_CH8, GRTC_CH9, GRTC_CH10, GRTC_CH11, // PWM PWM20, PWM21, PWM22, // SERIAL SERIAL00, SERIAL20, SERIAL21, SERIAL22, SERIAL30, // SAADC SAADC, // RADIO RADIO, // GPIOTE instances GPIOTE20, GPIOTE30, // GPIOTE channels GPIOTE20_CH0, GPIOTE20_CH1, GPIOTE20_CH2, GPIOTE20_CH3, GPIOTE20_CH4, GPIOTE20_CH5, GPIOTE20_CH6, GPIOTE20_CH7, GPIOTE30_CH0, GPIOTE30_CH1, GPIOTE30_CH2, GPIOTE30_CH3, // CRACEN #[cfg(feature = "_s")] CRACEN, #[cfg(feature = "_s")] // RRAMC RRAMC, // TEMP TEMP, // WDT #[cfg(feature = "_ns")] WDT, #[cfg(feature = "_s")] WDT0, #[cfg(feature = "_s")] WDT1, } 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); cfg_if::cfg_if! { if #[cfg(feature = "gpiote")] { impl_gpiote_pin!(P0_00, GPIOTE30); impl_gpiote_pin!(P0_01, GPIOTE30); impl_gpiote_pin!(P0_02, GPIOTE30); impl_gpiote_pin!(P0_03, GPIOTE30); impl_gpiote_pin!(P0_04, GPIOTE30); impl_gpiote_pin!(P0_05, GPIOTE30); impl_gpiote_pin!(P0_06, GPIOTE30); impl_gpiote_pin!(P1_00, GPIOTE20); impl_gpiote_pin!(P1_01, GPIOTE20); impl_gpiote_pin!(P1_02, GPIOTE20); impl_gpiote_pin!(P1_03, GPIOTE20); impl_gpiote_pin!(P1_04, GPIOTE20); impl_gpiote_pin!(P1_05, GPIOTE20); impl_gpiote_pin!(P1_06, GPIOTE20); impl_gpiote_pin!(P1_07, GPIOTE20); impl_gpiote_pin!(P1_08, GPIOTE20); impl_gpiote_pin!(P1_09, GPIOTE20); impl_gpiote_pin!(P1_10, GPIOTE20); impl_gpiote_pin!(P1_11, GPIOTE20); impl_gpiote_pin!(P1_12, GPIOTE20); impl_gpiote_pin!(P1_13, GPIOTE20); impl_gpiote_pin!(P1_14, GPIOTE20); impl_gpiote_pin!(P1_15, GPIOTE20); impl_gpiote_pin!(P1_16, GPIOTE20); } } #[cfg(feature = "_ns")] impl_wdt!(WDT, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT0, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT1, WDT30, WDT30, 1); // DPPI00 channels impl_ppi_channel!(PPI00_CH0, DPPIC00, 0 => configurable); impl_ppi_channel!(PPI00_CH1, DPPIC00, 1 => configurable); impl_ppi_channel!(PPI00_CH2, DPPIC00, 2 => configurable); impl_ppi_channel!(PPI00_CH3, DPPIC00, 3 => configurable); impl_ppi_channel!(PPI00_CH4, DPPIC00, 4 => configurable); impl_ppi_channel!(PPI00_CH5, DPPIC00, 5 => configurable); impl_ppi_channel!(PPI00_CH6, DPPIC00, 6 => configurable); impl_ppi_channel!(PPI00_CH7, DPPIC00, 7 => configurable); // DPPI10 channels impl_ppi_channel!(PPI10_CH0, DPPIC10, 0 => static); // DPPI20 channels impl_ppi_channel!(PPI20_CH0, DPPIC20, 0 => configurable); impl_ppi_channel!(PPI20_CH1, DPPIC20, 1 => configurable); impl_ppi_channel!(PPI20_CH2, DPPIC20, 2 => configurable); impl_ppi_channel!(PPI20_CH3, DPPIC20, 3 => configurable); impl_ppi_channel!(PPI20_CH4, DPPIC20, 4 => configurable); impl_ppi_channel!(PPI20_CH5, DPPIC20, 5 => configurable); impl_ppi_channel!(PPI20_CH6, DPPIC20, 6 => configurable); impl_ppi_channel!(PPI20_CH7, DPPIC20, 7 => configurable); impl_ppi_channel!(PPI20_CH8, DPPIC20, 8 => configurable); impl_ppi_channel!(PPI20_CH9, DPPIC20, 9 => configurable); impl_ppi_channel!(PPI20_CH10, DPPIC20, 10 => configurable); impl_ppi_channel!(PPI20_CH11, DPPIC20, 11 => configurable); impl_ppi_channel!(PPI20_CH12, DPPIC20, 12 => configurable); impl_ppi_channel!(PPI20_CH13, DPPIC20, 13 => configurable); impl_ppi_channel!(PPI20_CH14, DPPIC20, 14 => configurable); impl_ppi_channel!(PPI20_CH15, DPPIC20, 15 => configurable); // DPPI30 channels impl_ppi_channel!(PPI30_CH0, DPPIC30, 0 => configurable); impl_ppi_channel!(PPI30_CH1, DPPIC30, 1 => configurable); impl_ppi_channel!(PPI30_CH2, DPPIC30, 2 => configurable); impl_ppi_channel!(PPI30_CH3, DPPIC30, 3 => configurable); // DPPI00 groups impl_ppi_group!(PPI00_GROUP0, DPPIC00, 0); impl_ppi_group!(PPI00_GROUP1, DPPIC00, 1); // DPPI10 groups impl_ppi_group!(PPI10_GROUP0, DPPIC10, 0); // DPPI20 groups impl_ppi_group!(PPI20_GROUP0, DPPIC20, 0); impl_ppi_group!(PPI20_GROUP1, DPPIC20, 1); impl_ppi_group!(PPI20_GROUP2, DPPIC20, 2); impl_ppi_group!(PPI20_GROUP3, DPPIC20, 3); impl_ppi_group!(PPI20_GROUP4, DPPIC20, 4); impl_ppi_group!(PPI20_GROUP5, DPPIC20, 5); // DPPI30 groups impl_ppi_group!(PPI30_GROUP0, DPPIC30, 0); impl_ppi_group!(PPI30_GROUP1, DPPIC30, 1); impl_timer!(TIMER00, TIMER00, TIMER00); impl_timer!(TIMER10, TIMER10, TIMER10); impl_timer!(TIMER20, TIMER20, TIMER20); impl_timer!(TIMER21, TIMER21, TIMER21); impl_timer!(TIMER22, TIMER22, TIMER22); impl_timer!(TIMER23, TIMER23, TIMER23); impl_timer!(TIMER24, TIMER24, TIMER24); impl_twim!(SERIAL20, TWIM20, SERIAL20); impl_twim!(SERIAL21, TWIM21, SERIAL21); impl_twim!(SERIAL22, TWIM22, SERIAL22); impl_twim!(SERIAL30, TWIM30, SERIAL30); impl_twis!(SERIAL20, TWIS20, SERIAL20); impl_twis!(SERIAL21, TWIS21, SERIAL21); impl_twis!(SERIAL22, TWIS22, SERIAL22); impl_twis!(SERIAL30, TWIS30, SERIAL30); impl_pwm!(PWM20, PWM20, PWM20); impl_pwm!(PWM21, PWM21, PWM21); impl_pwm!(PWM22, PWM22, PWM22); #[cfg(feature = "_s")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_S.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); #[cfg(feature = "_ns")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_NS.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); impl_spim!(SERIAL20, SPIM20, SERIAL20, 16_000_000); impl_spim!(SERIAL21, SPIM21, SERIAL21, 16_000_000); impl_spim!(SERIAL22, SPIM22, SERIAL22, 16_000_000); impl_spim!(SERIAL30, SPIM30, SERIAL30, 16_000_000); impl_spis!(SERIAL20, SPIS20, SERIAL20); impl_spis!(SERIAL21, SPIS21, SERIAL21); impl_spis!(SERIAL22, SPIS22, SERIAL22); impl_spis!(SERIAL30, SPIS30, SERIAL30); impl_uarte!(SERIAL00, UARTE00, SERIAL00); impl_uarte!(SERIAL20, UARTE20, SERIAL20); impl_uarte!(SERIAL21, UARTE21, SERIAL21); impl_uarte!(SERIAL22, UARTE22, SERIAL22); impl_uarte!(SERIAL30, UARTE30, SERIAL30); // NB: SAADC uses "pin" abstraction, not "AIN" impl_saadc_input!(P1_04, 1, 4); impl_saadc_input!(P1_05, 1, 5); impl_saadc_input!(P1_06, 1, 6); impl_saadc_input!(P1_07, 1, 7); impl_saadc_input!(P1_11, 1, 11); impl_saadc_input!(P1_12, 1, 12); impl_saadc_input!(P1_13, 1, 13); impl_saadc_input!(P1_14, 1, 14); #[cfg(feature = "_s")] impl_cracen!(CRACEN, CRACEN, CRACEN); embassy_hal_internal::interrupt_mod!( SWI00, SWI01, SWI02, SWI03, SPU00, MPC00, AAR00_CCM00, ECB00, CRACEN, SERIAL00, RRAMC, VPR00, CTRLAP, TIMER00, SPU10, TIMER10, 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, COMP_LPCOMP, WDT30, WDT31, GPIOTE30_0, GPIOTE30_1, CLOCK_POWER, ); ================================================ FILE: embassy-nrf/src/chips/nrf54l10_app.rs ================================================ /// 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, 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, 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::{ FICR_NS as FICR, 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, 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, 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; // 1.5 MB NVM #[allow(unused)] pub const FLASH_SIZE: usize = 1524 * 1024; embassy_hal_internal::peripherals! { // PPI PPI00_CH0, PPI00_CH1, PPI00_CH2, PPI00_CH3, PPI00_CH4, PPI00_CH5, PPI00_CH6, PPI00_CH7, PPI10_CH0, PPI10_CH1, PPI10_CH2, PPI10_CH3, PPI10_CH4, PPI10_CH5, PPI10_CH6, PPI10_CH7, PPI10_CH8, PPI10_CH9, PPI10_CH10, PPI10_CH11, PPI10_CH12, PPI10_CH13, PPI10_CH14, PPI10_CH15, PPI10_CH16, PPI10_CH17, PPI10_CH18, PPI10_CH19, PPI10_CH20, PPI10_CH21, PPI10_CH22, PPI10_CH23, PPI20_CH0, PPI20_CH1, PPI20_CH2, PPI20_CH3, PPI20_CH4, PPI20_CH5, PPI20_CH6, PPI20_CH7, PPI20_CH8, PPI20_CH9, PPI20_CH10, PPI20_CH11, PPI20_CH12, PPI20_CH13, PPI20_CH14, PPI20_CH15, PPI30_CH0, PPI30_CH1, PPI30_CH2, PPI30_CH3, PPI00_GROUP0, PPI00_GROUP1, PPI10_GROUP0, PPI10_GROUP1, PPI10_GROUP2, PPI10_GROUP3, PPI10_GROUP4, PPI10_GROUP5, PPI20_GROUP0, PPI20_GROUP1, PPI20_GROUP2, PPI20_GROUP3, PPI20_GROUP4, PPI20_GROUP5, PPI30_GROUP0, PPI30_GROUP1, // PPI BRIDGE channels PPIB00_CH0, PPIB00_CH1, PPIB00_CH2, PPIB00_CH3, PPIB00_CH4, PPIB00_CH5, PPIB00_CH6, PPIB00_CH7, PPIB01_CH0, PPIB01_CH1, PPIB01_CH2, PPIB01_CH3, PPIB01_CH4, PPIB01_CH5, PPIB01_CH6, PPIB01_CH7, PPIB10_CH0, PPIB10_CH1, PPIB10_CH2, PPIB10_CH3, PPIB10_CH4, PPIB10_CH5, PPIB10_CH6, PPIB10_CH7, PPIB11_CH0, PPIB11_CH1, PPIB11_CH2, PPIB11_CH3, PPIB11_CH4, PPIB11_CH5, PPIB11_CH6, PPIB11_CH7, PPIB11_CH8, PPIB11_CH9, PPIB11_CH10, PPIB11_CH11, PPIB11_CH12, PPIB11_CH13, PPIB11_CH14, PPIB11_CH15, PPIB20_CH0, PPIB20_CH1, PPIB20_CH2, PPIB20_CH3, PPIB20_CH4, PPIB20_CH5, PPIB20_CH6, PPIB20_CH7, PPIB21_CH0, PPIB21_CH1, PPIB21_CH2, PPIB21_CH3, PPIB21_CH4, PPIB21_CH5, PPIB21_CH6, PPIB21_CH7, PPIB21_CH8, PPIB21_CH9, PPIB21_CH10, PPIB21_CH11, PPIB21_CH12, PPIB21_CH13, PPIB21_CH14, PPIB21_CH15, PPIB22_CH0, PPIB22_CH1, PPIB22_CH2, PPIB22_CH3, PPIB30_CH0, PPIB30_CH1, PPIB30_CH2, PPIB30_CH3, // Timers TIMER00, TIMER10, TIMER20, TIMER21, TIMER22, TIMER23, TIMER24, // 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, // GRTC GRTC_CH0, #[cfg(not(feature = "time-driver-grtc"))] GRTC_CH1, GRTC_CH2, GRTC_CH3, GRTC_CH4, GRTC_CH5, GRTC_CH6, GRTC_CH7, GRTC_CH8, GRTC_CH9, GRTC_CH10, GRTC_CH11, // PWM PWM20, PWM21, PWM22, // SERIAL SERIAL00, SERIAL20, SERIAL21, SERIAL22, SERIAL30, // SAADC SAADC, // RADIO RADIO, // GPIOTE instances GPIOTE20, GPIOTE30, // GPIOTE channels GPIOTE20_CH0, GPIOTE20_CH1, GPIOTE20_CH2, GPIOTE20_CH3, GPIOTE20_CH4, GPIOTE20_CH5, GPIOTE20_CH6, GPIOTE20_CH7, GPIOTE30_CH0, GPIOTE30_CH1, GPIOTE30_CH2, GPIOTE30_CH3, // CRACEN #[cfg(feature = "_s")] CRACEN, #[cfg(feature = "_s")] // RRAMC RRAMC, // TEMP TEMP, // WDT #[cfg(feature = "_ns")] WDT, #[cfg(feature = "_s")] WDT0, #[cfg(feature = "_s")] WDT1, } 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); cfg_if::cfg_if! { if #[cfg(feature = "gpiote")] { impl_gpiote_pin!(P0_00, GPIOTE30); impl_gpiote_pin!(P0_01, GPIOTE30); impl_gpiote_pin!(P0_02, GPIOTE30); impl_gpiote_pin!(P0_03, GPIOTE30); impl_gpiote_pin!(P0_04, GPIOTE30); impl_gpiote_pin!(P0_05, GPIOTE30); impl_gpiote_pin!(P0_06, GPIOTE30); impl_gpiote_pin!(P1_00, GPIOTE20); impl_gpiote_pin!(P1_01, GPIOTE20); impl_gpiote_pin!(P1_02, GPIOTE20); impl_gpiote_pin!(P1_03, GPIOTE20); impl_gpiote_pin!(P1_04, GPIOTE20); impl_gpiote_pin!(P1_05, GPIOTE20); impl_gpiote_pin!(P1_06, GPIOTE20); impl_gpiote_pin!(P1_07, GPIOTE20); impl_gpiote_pin!(P1_08, GPIOTE20); impl_gpiote_pin!(P1_09, GPIOTE20); impl_gpiote_pin!(P1_10, GPIOTE20); impl_gpiote_pin!(P1_11, GPIOTE20); impl_gpiote_pin!(P1_12, GPIOTE20); impl_gpiote_pin!(P1_13, GPIOTE20); impl_gpiote_pin!(P1_14, GPIOTE20); impl_gpiote_pin!(P1_15, GPIOTE20); impl_gpiote_pin!(P1_16, GPIOTE20); } } #[cfg(feature = "_ns")] impl_wdt!(WDT, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT0, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT1, WDT30, WDT30, 1); // DPPI00 channels impl_ppi_channel!(PPI00_CH0, DPPIC00, 0 => configurable); impl_ppi_channel!(PPI00_CH1, DPPIC00, 1 => configurable); impl_ppi_channel!(PPI00_CH2, DPPIC00, 2 => configurable); impl_ppi_channel!(PPI00_CH3, DPPIC00, 3 => configurable); impl_ppi_channel!(PPI00_CH4, DPPIC00, 4 => configurable); impl_ppi_channel!(PPI00_CH5, DPPIC00, 5 => configurable); impl_ppi_channel!(PPI00_CH6, DPPIC00, 6 => configurable); impl_ppi_channel!(PPI00_CH7, DPPIC00, 7 => configurable); // DPPI10 channels impl_ppi_channel!(PPI10_CH0, DPPIC10, 0 => static); // DPPI20 channels impl_ppi_channel!(PPI20_CH0, DPPIC20, 0 => configurable); impl_ppi_channel!(PPI20_CH1, DPPIC20, 1 => configurable); impl_ppi_channel!(PPI20_CH2, DPPIC20, 2 => configurable); impl_ppi_channel!(PPI20_CH3, DPPIC20, 3 => configurable); impl_ppi_channel!(PPI20_CH4, DPPIC20, 4 => configurable); impl_ppi_channel!(PPI20_CH5, DPPIC20, 5 => configurable); impl_ppi_channel!(PPI20_CH6, DPPIC20, 6 => configurable); impl_ppi_channel!(PPI20_CH7, DPPIC20, 7 => configurable); impl_ppi_channel!(PPI20_CH8, DPPIC20, 8 => configurable); impl_ppi_channel!(PPI20_CH9, DPPIC20, 9 => configurable); impl_ppi_channel!(PPI20_CH10, DPPIC20, 10 => configurable); impl_ppi_channel!(PPI20_CH11, DPPIC20, 11 => configurable); impl_ppi_channel!(PPI20_CH12, DPPIC20, 12 => configurable); impl_ppi_channel!(PPI20_CH13, DPPIC20, 13 => configurable); impl_ppi_channel!(PPI20_CH14, DPPIC20, 14 => configurable); impl_ppi_channel!(PPI20_CH15, DPPIC20, 15 => configurable); // DPPI30 channels impl_ppi_channel!(PPI30_CH0, DPPIC30, 0 => configurable); impl_ppi_channel!(PPI30_CH1, DPPIC30, 1 => configurable); impl_ppi_channel!(PPI30_CH2, DPPIC30, 2 => configurable); impl_ppi_channel!(PPI30_CH3, DPPIC30, 3 => configurable); // DPPI00 groups impl_ppi_group!(PPI00_GROUP0, DPPIC00, 0); impl_ppi_group!(PPI00_GROUP1, DPPIC00, 1); // DPPI10 groups impl_ppi_group!(PPI10_GROUP0, DPPIC10, 0); // DPPI20 groups impl_ppi_group!(PPI20_GROUP0, DPPIC20, 0); impl_ppi_group!(PPI20_GROUP1, DPPIC20, 1); impl_ppi_group!(PPI20_GROUP2, DPPIC20, 2); impl_ppi_group!(PPI20_GROUP3, DPPIC20, 3); impl_ppi_group!(PPI20_GROUP4, DPPIC20, 4); impl_ppi_group!(PPI20_GROUP5, DPPIC20, 5); // DPPI30 groups impl_ppi_group!(PPI30_GROUP0, DPPIC30, 0); impl_ppi_group!(PPI30_GROUP1, DPPIC30, 1); impl_timer!(TIMER00, TIMER00, TIMER00); impl_timer!(TIMER10, TIMER10, TIMER10); impl_timer!(TIMER20, TIMER20, TIMER20); impl_timer!(TIMER21, TIMER21, TIMER21); impl_timer!(TIMER22, TIMER22, TIMER22); impl_timer!(TIMER23, TIMER23, TIMER23); impl_timer!(TIMER24, TIMER24, TIMER24); impl_twim!(SERIAL20, TWIM20, SERIAL20); impl_twim!(SERIAL21, TWIM21, SERIAL21); impl_twim!(SERIAL22, TWIM22, SERIAL22); impl_twim!(SERIAL30, TWIM30, SERIAL30); impl_twis!(SERIAL20, TWIS20, SERIAL20); impl_twis!(SERIAL21, TWIS21, SERIAL21); impl_twis!(SERIAL22, TWIS22, SERIAL22); impl_twis!(SERIAL30, TWIS30, SERIAL30); impl_pwm!(PWM20, PWM20, PWM20); impl_pwm!(PWM21, PWM21, PWM21); impl_pwm!(PWM22, PWM22, PWM22); #[cfg(feature = "_s")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_S.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); #[cfg(feature = "_ns")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_NS.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); impl_spim!(SERIAL20, SPIM20, SERIAL20, 16_000_000); impl_spim!(SERIAL21, SPIM21, SERIAL21, 16_000_000); impl_spim!(SERIAL22, SPIM22, SERIAL22, 16_000_000); impl_spim!(SERIAL30, SPIM30, SERIAL30, 16_000_000); impl_spis!(SERIAL20, SPIS20, SERIAL20); impl_spis!(SERIAL21, SPIS21, SERIAL21); impl_spis!(SERIAL22, SPIS22, SERIAL22); impl_spis!(SERIAL30, SPIS30, SERIAL30); impl_uarte!(SERIAL00, UARTE00, SERIAL00); impl_uarte!(SERIAL20, UARTE20, SERIAL20); impl_uarte!(SERIAL21, UARTE21, SERIAL21); impl_uarte!(SERIAL22, UARTE22, SERIAL22); impl_uarte!(SERIAL30, UARTE30, SERIAL30); // NB: SAADC uses "pin" abstraction, not "AIN" impl_saadc_input!(P1_04, 1, 4); impl_saadc_input!(P1_05, 1, 5); impl_saadc_input!(P1_06, 1, 6); impl_saadc_input!(P1_07, 1, 7); impl_saadc_input!(P1_11, 1, 11); impl_saadc_input!(P1_12, 1, 12); impl_saadc_input!(P1_13, 1, 13); impl_saadc_input!(P1_14, 1, 14); #[cfg(feature = "_s")] impl_cracen!(CRACEN, CRACEN, CRACEN); embassy_hal_internal::interrupt_mod!( SWI00, SWI01, SWI02, SWI03, SPU00, MPC00, AAR00_CCM00, ECB00, CRACEN, SERIAL00, RRAMC, VPR00, CTRLAP, TIMER00, SPU10, TIMER10, 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, COMP_LPCOMP, WDT30, WDT31, GPIOTE30_0, GPIOTE30_1, CLOCK_POWER, ); ================================================ FILE: embassy-nrf/src/chips/nrf54l15_app.rs ================================================ /// 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, 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, 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::{ FICR_NS as FICR, 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, 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, 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; // 1.5 MB NVM #[allow(unused)] pub const FLASH_SIZE: usize = 1524 * 1024; embassy_hal_internal::peripherals! { // PPI PPI00_CH0, PPI00_CH1, PPI00_CH2, PPI00_CH3, PPI00_CH4, PPI00_CH5, PPI00_CH6, PPI00_CH7, PPI10_CH0, PPI10_CH1, PPI10_CH2, PPI10_CH3, PPI10_CH4, PPI10_CH5, PPI10_CH6, PPI10_CH7, PPI10_CH8, PPI10_CH9, PPI10_CH10, PPI10_CH11, PPI10_CH12, PPI10_CH13, PPI10_CH14, PPI10_CH15, PPI10_CH16, PPI10_CH17, PPI10_CH18, PPI10_CH19, PPI10_CH20, PPI10_CH21, PPI10_CH22, PPI10_CH23, PPI20_CH0, PPI20_CH1, PPI20_CH2, PPI20_CH3, PPI20_CH4, PPI20_CH5, PPI20_CH6, PPI20_CH7, PPI20_CH8, PPI20_CH9, PPI20_CH10, PPI20_CH11, PPI20_CH12, PPI20_CH13, PPI20_CH14, PPI20_CH15, PPI30_CH0, PPI30_CH1, PPI30_CH2, PPI30_CH3, PPI00_GROUP0, PPI00_GROUP1, PPI10_GROUP0, PPI10_GROUP1, PPI10_GROUP2, PPI10_GROUP3, PPI10_GROUP4, PPI10_GROUP5, PPI20_GROUP0, PPI20_GROUP1, PPI20_GROUP2, PPI20_GROUP3, PPI20_GROUP4, PPI20_GROUP5, PPI30_GROUP0, PPI30_GROUP1, // PPI BRIDGE channels PPIB00_CH0, PPIB00_CH1, PPIB00_CH2, PPIB00_CH3, PPIB00_CH4, PPIB00_CH5, PPIB00_CH6, PPIB00_CH7, PPIB01_CH0, PPIB01_CH1, PPIB01_CH2, PPIB01_CH3, PPIB01_CH4, PPIB01_CH5, PPIB01_CH6, PPIB01_CH7, PPIB10_CH0, PPIB10_CH1, PPIB10_CH2, PPIB10_CH3, PPIB10_CH4, PPIB10_CH5, PPIB10_CH6, PPIB10_CH7, PPIB11_CH0, PPIB11_CH1, PPIB11_CH2, PPIB11_CH3, PPIB11_CH4, PPIB11_CH5, PPIB11_CH6, PPIB11_CH7, PPIB11_CH8, PPIB11_CH9, PPIB11_CH10, PPIB11_CH11, PPIB11_CH12, PPIB11_CH13, PPIB11_CH14, PPIB11_CH15, PPIB20_CH0, PPIB20_CH1, PPIB20_CH2, PPIB20_CH3, PPIB20_CH4, PPIB20_CH5, PPIB20_CH6, PPIB20_CH7, PPIB21_CH0, PPIB21_CH1, PPIB21_CH2, PPIB21_CH3, PPIB21_CH4, PPIB21_CH5, PPIB21_CH6, PPIB21_CH7, PPIB21_CH8, PPIB21_CH9, PPIB21_CH10, PPIB21_CH11, PPIB21_CH12, PPIB21_CH13, PPIB21_CH14, PPIB21_CH15, PPIB22_CH0, PPIB22_CH1, PPIB22_CH2, PPIB22_CH3, PPIB30_CH0, PPIB30_CH1, PPIB30_CH2, PPIB30_CH3, // Timers TIMER00, TIMER10, TIMER20, TIMER21, TIMER22, TIMER23, TIMER24, // 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, // GRTC GRTC_CH0, #[cfg(not(feature = "time-driver-grtc"))] GRTC_CH1, GRTC_CH2, GRTC_CH3, GRTC_CH4, GRTC_CH5, GRTC_CH6, GRTC_CH7, GRTC_CH8, GRTC_CH9, GRTC_CH10, GRTC_CH11, // PWM PWM20, PWM21, PWM22, // SERIAL SERIAL00, SERIAL20, SERIAL21, SERIAL22, SERIAL30, // SAADC SAADC, // RADIO RADIO, // GPIOTE instances GPIOTE20, GPIOTE30, // GPIOTE channels GPIOTE20_CH0, GPIOTE20_CH1, GPIOTE20_CH2, GPIOTE20_CH3, GPIOTE20_CH4, GPIOTE20_CH5, GPIOTE20_CH6, GPIOTE20_CH7, GPIOTE30_CH0, GPIOTE30_CH1, GPIOTE30_CH2, GPIOTE30_CH3, // CRACEN #[cfg(feature = "_s")] CRACEN, #[cfg(feature = "_s")] // RRAMC RRAMC, // TEMP TEMP, // WDT #[cfg(feature = "_ns")] WDT, #[cfg(feature = "_s")] WDT0, #[cfg(feature = "_s")] WDT1, } 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); cfg_if::cfg_if! { if #[cfg(feature = "gpiote")] { impl_gpiote_pin!(P0_00, GPIOTE30); impl_gpiote_pin!(P0_01, GPIOTE30); impl_gpiote_pin!(P0_02, GPIOTE30); impl_gpiote_pin!(P0_03, GPIOTE30); impl_gpiote_pin!(P0_04, GPIOTE30); impl_gpiote_pin!(P0_05, GPIOTE30); impl_gpiote_pin!(P0_06, GPIOTE30); impl_gpiote_pin!(P1_00, GPIOTE20); impl_gpiote_pin!(P1_01, GPIOTE20); impl_gpiote_pin!(P1_02, GPIOTE20); impl_gpiote_pin!(P1_03, GPIOTE20); impl_gpiote_pin!(P1_04, GPIOTE20); impl_gpiote_pin!(P1_05, GPIOTE20); impl_gpiote_pin!(P1_06, GPIOTE20); impl_gpiote_pin!(P1_07, GPIOTE20); impl_gpiote_pin!(P1_08, GPIOTE20); impl_gpiote_pin!(P1_09, GPIOTE20); impl_gpiote_pin!(P1_10, GPIOTE20); impl_gpiote_pin!(P1_11, GPIOTE20); impl_gpiote_pin!(P1_12, GPIOTE20); impl_gpiote_pin!(P1_13, GPIOTE20); impl_gpiote_pin!(P1_14, GPIOTE20); impl_gpiote_pin!(P1_15, GPIOTE20); impl_gpiote_pin!(P1_16, GPIOTE20); } } #[cfg(feature = "_ns")] impl_wdt!(WDT, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT0, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT1, WDT30, WDT30, 1); // DPPI00 channels impl_ppi_channel!(PPI00_CH0, DPPIC00, 0 => configurable); impl_ppi_channel!(PPI00_CH1, DPPIC00, 1 => configurable); impl_ppi_channel!(PPI00_CH2, DPPIC00, 2 => configurable); impl_ppi_channel!(PPI00_CH3, DPPIC00, 3 => configurable); impl_ppi_channel!(PPI00_CH4, DPPIC00, 4 => configurable); impl_ppi_channel!(PPI00_CH5, DPPIC00, 5 => configurable); impl_ppi_channel!(PPI00_CH6, DPPIC00, 6 => configurable); impl_ppi_channel!(PPI00_CH7, DPPIC00, 7 => configurable); // DPPI10 channels impl_ppi_channel!(PPI10_CH0, DPPIC10, 0 => static); // DPPI20 channels impl_ppi_channel!(PPI20_CH0, DPPIC20, 0 => configurable); impl_ppi_channel!(PPI20_CH1, DPPIC20, 1 => configurable); impl_ppi_channel!(PPI20_CH2, DPPIC20, 2 => configurable); impl_ppi_channel!(PPI20_CH3, DPPIC20, 3 => configurable); impl_ppi_channel!(PPI20_CH4, DPPIC20, 4 => configurable); impl_ppi_channel!(PPI20_CH5, DPPIC20, 5 => configurable); impl_ppi_channel!(PPI20_CH6, DPPIC20, 6 => configurable); impl_ppi_channel!(PPI20_CH7, DPPIC20, 7 => configurable); impl_ppi_channel!(PPI20_CH8, DPPIC20, 8 => configurable); impl_ppi_channel!(PPI20_CH9, DPPIC20, 9 => configurable); impl_ppi_channel!(PPI20_CH10, DPPIC20, 10 => configurable); impl_ppi_channel!(PPI20_CH11, DPPIC20, 11 => configurable); impl_ppi_channel!(PPI20_CH12, DPPIC20, 12 => configurable); impl_ppi_channel!(PPI20_CH13, DPPIC20, 13 => configurable); impl_ppi_channel!(PPI20_CH14, DPPIC20, 14 => configurable); impl_ppi_channel!(PPI20_CH15, DPPIC20, 15 => configurable); // DPPI30 channels impl_ppi_channel!(PPI30_CH0, DPPIC30, 0 => configurable); impl_ppi_channel!(PPI30_CH1, DPPIC30, 1 => configurable); impl_ppi_channel!(PPI30_CH2, DPPIC30, 2 => configurable); impl_ppi_channel!(PPI30_CH3, DPPIC30, 3 => configurable); // DPPI00 groups impl_ppi_group!(PPI00_GROUP0, DPPIC00, 0); impl_ppi_group!(PPI00_GROUP1, DPPIC00, 1); // DPPI10 groups impl_ppi_group!(PPI10_GROUP0, DPPIC10, 0); // DPPI20 groups impl_ppi_group!(PPI20_GROUP0, DPPIC20, 0); impl_ppi_group!(PPI20_GROUP1, DPPIC20, 1); impl_ppi_group!(PPI20_GROUP2, DPPIC20, 2); impl_ppi_group!(PPI20_GROUP3, DPPIC20, 3); impl_ppi_group!(PPI20_GROUP4, DPPIC20, 4); impl_ppi_group!(PPI20_GROUP5, DPPIC20, 5); // DPPI30 groups impl_ppi_group!(PPI30_GROUP0, DPPIC30, 0); impl_ppi_group!(PPI30_GROUP1, DPPIC30, 1); impl_timer!(TIMER00, TIMER00, TIMER00); impl_timer!(TIMER10, TIMER10, TIMER10); impl_timer!(TIMER20, TIMER20, TIMER20); impl_timer!(TIMER21, TIMER21, TIMER21); impl_timer!(TIMER22, TIMER22, TIMER22); impl_timer!(TIMER23, TIMER23, TIMER23); impl_timer!(TIMER24, TIMER24, TIMER24); impl_twim!(SERIAL20, TWIM20, SERIAL20); impl_twim!(SERIAL21, TWIM21, SERIAL21); impl_twim!(SERIAL22, TWIM22, SERIAL22); impl_twim!(SERIAL30, TWIM30, SERIAL30); impl_twis!(SERIAL20, TWIS20, SERIAL20); impl_twis!(SERIAL21, TWIS21, SERIAL21); impl_twis!(SERIAL22, TWIS22, SERIAL22); impl_twis!(SERIAL30, TWIS30, SERIAL30); impl_pwm!(PWM20, PWM20, PWM20); impl_pwm!(PWM21, PWM21, PWM21); impl_pwm!(PWM22, PWM22, PWM22); #[cfg(feature = "_s")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_S.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); #[cfg(feature = "_ns")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_NS.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); impl_spim!(SERIAL20, SPIM20, SERIAL20, 16_000_000); impl_spim!(SERIAL21, SPIM21, SERIAL21, 16_000_000); impl_spim!(SERIAL22, SPIM22, SERIAL22, 16_000_000); impl_spim!(SERIAL30, SPIM30, SERIAL30, 16_000_000); impl_spis!(SERIAL20, SPIS20, SERIAL20); impl_spis!(SERIAL21, SPIS21, SERIAL21); impl_spis!(SERIAL22, SPIS22, SERIAL22); impl_spis!(SERIAL30, SPIS30, SERIAL30); impl_uarte!(SERIAL00, UARTE00, SERIAL00); impl_uarte!(SERIAL20, UARTE20, SERIAL20); impl_uarte!(SERIAL21, UARTE21, SERIAL21); impl_uarte!(SERIAL22, UARTE22, SERIAL22); impl_uarte!(SERIAL30, UARTE30, SERIAL30); // NB: SAADC uses "pin" abstraction, not "AIN" impl_saadc_input!(P1_04, 1, 4); impl_saadc_input!(P1_05, 1, 5); impl_saadc_input!(P1_06, 1, 6); impl_saadc_input!(P1_07, 1, 7); impl_saadc_input!(P1_11, 1, 11); impl_saadc_input!(P1_12, 1, 12); impl_saadc_input!(P1_13, 1, 13); impl_saadc_input!(P1_14, 1, 14); #[cfg(feature = "_s")] impl_cracen!(CRACEN, CRACEN, CRACEN); embassy_hal_internal::interrupt_mod!( SWI00, SWI01, SWI02, SWI03, SPU00, MPC00, AAR00_CCM00, ECB00, CRACEN, SERIAL00, RRAMC, VPR00, CTRLAP, TIMER00, SPU10, TIMER10, 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, COMP_LPCOMP, WDT30, WDT31, GPIOTE30_0, GPIOTE30_1, CLOCK_POWER, ); ================================================ FILE: embassy-nrf/src/chips/nrf54lm20_app.rs ================================================ /// 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, 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, 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::{ FICR_NS as FICR, 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, 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, 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, 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; // 2 MB NVM #[allow(unused)] pub const FLASH_SIZE: usize = 2048 * 1024; embassy_hal_internal::peripherals! { // PPI PPI00_CH0, PPI00_CH1, PPI00_CH2, PPI00_CH3, PPI00_CH4, PPI00_CH5, PPI00_CH6, PPI00_CH7, PPI10_CH0, PPI10_CH1, PPI10_CH2, PPI10_CH3, PPI10_CH4, PPI10_CH5, PPI10_CH6, PPI10_CH7, PPI10_CH8, PPI10_CH9, PPI10_CH10, PPI10_CH11, PPI10_CH12, PPI10_CH13, PPI10_CH14, PPI10_CH15, PPI10_CH16, PPI10_CH17, PPI10_CH18, PPI10_CH19, PPI10_CH20, PPI10_CH21, PPI10_CH22, PPI10_CH23, PPI20_CH0, PPI20_CH1, PPI20_CH2, PPI20_CH3, PPI20_CH4, PPI20_CH5, PPI20_CH6, PPI20_CH7, PPI20_CH8, PPI20_CH9, PPI20_CH10, PPI20_CH11, PPI20_CH12, PPI20_CH13, PPI20_CH14, PPI20_CH15, PPI30_CH0, PPI30_CH1, PPI30_CH2, PPI30_CH3, PPI00_GROUP0, PPI00_GROUP1, PPI10_GROUP0, PPI10_GROUP1, PPI10_GROUP2, PPI10_GROUP3, PPI10_GROUP4, PPI10_GROUP5, PPI20_GROUP0, PPI20_GROUP1, PPI20_GROUP2, PPI20_GROUP3, PPI20_GROUP4, PPI20_GROUP5, PPI30_GROUP0, PPI30_GROUP1, // PPI BRIDGE channels PPIB00_CH0, PPIB00_CH1, PPIB00_CH2, PPIB00_CH3, PPIB00_CH4, PPIB00_CH5, PPIB00_CH6, PPIB00_CH7, PPIB01_CH0, PPIB01_CH1, PPIB01_CH2, PPIB01_CH3, PPIB01_CH4, PPIB01_CH5, PPIB01_CH6, PPIB01_CH7, PPIB10_CH0, PPIB10_CH1, PPIB10_CH2, PPIB10_CH3, PPIB10_CH4, PPIB10_CH5, PPIB10_CH6, PPIB10_CH7, PPIB11_CH0, PPIB11_CH1, PPIB11_CH2, PPIB11_CH3, PPIB11_CH4, PPIB11_CH5, PPIB11_CH6, PPIB11_CH7, PPIB11_CH8, PPIB11_CH9, PPIB11_CH10, PPIB11_CH11, PPIB11_CH12, PPIB11_CH13, PPIB11_CH14, PPIB11_CH15, PPIB20_CH0, PPIB20_CH1, PPIB20_CH2, PPIB20_CH3, PPIB20_CH4, PPIB20_CH5, PPIB20_CH6, PPIB20_CH7, PPIB21_CH0, PPIB21_CH1, PPIB21_CH2, PPIB21_CH3, PPIB21_CH4, PPIB21_CH5, PPIB21_CH6, PPIB21_CH7, PPIB21_CH8, PPIB21_CH9, PPIB21_CH10, PPIB21_CH11, PPIB21_CH12, PPIB21_CH13, PPIB21_CH14, PPIB21_CH15, PPIB22_CH0, PPIB22_CH1, PPIB22_CH2, PPIB22_CH3, PPIB30_CH0, PPIB30_CH1, PPIB30_CH2, PPIB30_CH3, // Timers TIMER00, TIMER10, TIMER20, TIMER21, TIMER22, TIMER23, TIMER24, // GPIO port 0 P0_00, P0_01, P0_02, P0_03, P0_04, P0_05, P0_06, P0_07, P0_08, P0_09, // 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, P1_17, P1_18, P1_19, P1_20, P1_21, P1_22, P1_23, P1_24, P1_25, P1_26, P1_27, P1_28, P1_29, P1_30, P1_31, // 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, // GPIO port 3 P3_00, P3_01, P3_02, P3_03, P3_04, P3_05, P3_06, P3_07, P3_08, P3_09, P3_10, P3_11, P3_12, // GRTC GRTC_CH0, #[cfg(not(feature = "time-driver-grtc"))] GRTC_CH1, GRTC_CH2, GRTC_CH3, GRTC_CH4, GRTC_CH5, GRTC_CH6, GRTC_CH7, GRTC_CH8, GRTC_CH9, GRTC_CH10, GRTC_CH11, // PWM PWM20, PWM21, PWM22, // SERIAL SERIAL00, SERIAL20, SERIAL21, SERIAL22, SERIAL30, // SAADC SAADC, // RADIO RADIO, // GPIOTE instances GPIOTE20, GPIOTE30, // GPIOTE channels GPIOTE20_CH0, GPIOTE20_CH1, GPIOTE20_CH2, GPIOTE20_CH3, GPIOTE20_CH4, GPIOTE20_CH5, GPIOTE20_CH6, GPIOTE20_CH7, GPIOTE30_CH0, GPIOTE30_CH1, GPIOTE30_CH2, GPIOTE30_CH3, // CRACEN #[cfg(feature = "_s")] CRACEN, #[cfg(feature = "_s")] // RRAMC RRAMC, // TEMP TEMP, // WDT #[cfg(feature = "_ns")] WDT, #[cfg(feature = "_s")] WDT0, #[cfg(feature = "_s")] WDT1, } 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!(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!(P1_17, 1, 17); impl_pin!(P1_18, 1, 18); impl_pin!(P1_19, 1, 19); impl_pin!(P1_20, 1, 20); impl_pin!(P1_21, 1, 21); impl_pin!(P1_22, 1, 22); impl_pin!(P1_23, 1, 23); impl_pin!(P1_24, 1, 24); impl_pin!(P1_25, 1, 25); impl_pin!(P1_26, 1, 26); impl_pin!(P1_27, 1, 27); impl_pin!(P1_28, 1, 28); impl_pin!(P1_29, 1, 29); impl_pin!(P1_30, 1, 30); impl_pin!(P1_31, 1, 31); 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); impl_pin!(P3_00, 3, 0); impl_pin!(P3_01, 3, 1); impl_pin!(P3_02, 3, 2); impl_pin!(P3_03, 3, 3); impl_pin!(P3_04, 3, 4); impl_pin!(P3_05, 3, 5); impl_pin!(P3_06, 3, 6); impl_pin!(P3_07, 3, 7); impl_pin!(P3_08, 3, 8); impl_pin!(P3_09, 3, 9); impl_pin!(P3_10, 3, 10); impl_pin!(P3_11, 3, 11); impl_pin!(P3_12, 3, 12); cfg_if::cfg_if! { if #[cfg(feature = "gpiote")] { impl_gpiote_pin!(P0_00, GPIOTE30); impl_gpiote_pin!(P0_01, GPIOTE30); impl_gpiote_pin!(P0_02, GPIOTE30); impl_gpiote_pin!(P0_03, GPIOTE30); impl_gpiote_pin!(P0_04, GPIOTE30); impl_gpiote_pin!(P0_05, GPIOTE30); impl_gpiote_pin!(P0_06, GPIOTE30); impl_gpiote_pin!(P0_07, GPIOTE30); impl_gpiote_pin!(P0_08, GPIOTE30); impl_gpiote_pin!(P0_09, GPIOTE30); impl_gpiote_pin!(P1_00, GPIOTE20); impl_gpiote_pin!(P1_01, GPIOTE20); impl_gpiote_pin!(P1_02, GPIOTE20); impl_gpiote_pin!(P1_03, GPIOTE20); impl_gpiote_pin!(P1_04, GPIOTE20); impl_gpiote_pin!(P1_05, GPIOTE20); impl_gpiote_pin!(P1_06, GPIOTE20); impl_gpiote_pin!(P1_07, GPIOTE20); impl_gpiote_pin!(P1_08, GPIOTE20); impl_gpiote_pin!(P1_09, GPIOTE20); impl_gpiote_pin!(P1_10, GPIOTE20); impl_gpiote_pin!(P1_11, GPIOTE20); impl_gpiote_pin!(P1_12, GPIOTE20); impl_gpiote_pin!(P1_13, GPIOTE20); impl_gpiote_pin!(P1_14, GPIOTE20); impl_gpiote_pin!(P1_15, GPIOTE20); impl_gpiote_pin!(P1_16, GPIOTE20); impl_gpiote_pin!(P1_17, GPIOTE20); impl_gpiote_pin!(P1_18, GPIOTE20); impl_gpiote_pin!(P1_19, GPIOTE20); impl_gpiote_pin!(P1_20, GPIOTE20); impl_gpiote_pin!(P1_21, GPIOTE20); impl_gpiote_pin!(P1_22, GPIOTE20); impl_gpiote_pin!(P1_23, GPIOTE20); impl_gpiote_pin!(P1_24, GPIOTE20); impl_gpiote_pin!(P1_25, GPIOTE20); impl_gpiote_pin!(P1_26, GPIOTE20); impl_gpiote_pin!(P1_27, GPIOTE20); impl_gpiote_pin!(P1_28, GPIOTE20); impl_gpiote_pin!(P1_29, GPIOTE20); impl_gpiote_pin!(P1_30, GPIOTE20); impl_gpiote_pin!(P1_31, GPIOTE20); impl_gpiote_pin!(P3_00, GPIOTE20); impl_gpiote_pin!(P3_01, GPIOTE20); impl_gpiote_pin!(P3_02, GPIOTE20); impl_gpiote_pin!(P3_03, GPIOTE20); impl_gpiote_pin!(P3_04, GPIOTE20); impl_gpiote_pin!(P3_05, GPIOTE20); impl_gpiote_pin!(P3_06, GPIOTE20); impl_gpiote_pin!(P3_07, GPIOTE20); impl_gpiote_pin!(P3_08, GPIOTE20); impl_gpiote_pin!(P3_09, GPIOTE20); impl_gpiote_pin!(P3_10, GPIOTE20); impl_gpiote_pin!(P3_11, GPIOTE20); impl_gpiote_pin!(P3_12, GPIOTE20); } } #[cfg(feature = "_ns")] impl_wdt!(WDT, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT0, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT1, WDT30, WDT30, 1); // DPPI00 channels impl_ppi_channel!(PPI00_CH0, DPPIC00, 0 => configurable); impl_ppi_channel!(PPI00_CH1, DPPIC00, 1 => configurable); impl_ppi_channel!(PPI00_CH2, DPPIC00, 2 => configurable); impl_ppi_channel!(PPI00_CH3, DPPIC00, 3 => configurable); impl_ppi_channel!(PPI00_CH4, DPPIC00, 4 => configurable); impl_ppi_channel!(PPI00_CH5, DPPIC00, 5 => configurable); impl_ppi_channel!(PPI00_CH6, DPPIC00, 6 => configurable); impl_ppi_channel!(PPI00_CH7, DPPIC00, 7 => configurable); // DPPI10 channels impl_ppi_channel!(PPI10_CH0, DPPIC10, 0 => static); // DPPI20 channels impl_ppi_channel!(PPI20_CH0, DPPIC20, 0 => configurable); impl_ppi_channel!(PPI20_CH1, DPPIC20, 1 => configurable); impl_ppi_channel!(PPI20_CH2, DPPIC20, 2 => configurable); impl_ppi_channel!(PPI20_CH3, DPPIC20, 3 => configurable); impl_ppi_channel!(PPI20_CH4, DPPIC20, 4 => configurable); impl_ppi_channel!(PPI20_CH5, DPPIC20, 5 => configurable); impl_ppi_channel!(PPI20_CH6, DPPIC20, 6 => configurable); impl_ppi_channel!(PPI20_CH7, DPPIC20, 7 => configurable); impl_ppi_channel!(PPI20_CH8, DPPIC20, 8 => configurable); impl_ppi_channel!(PPI20_CH9, DPPIC20, 9 => configurable); impl_ppi_channel!(PPI20_CH10, DPPIC20, 10 => configurable); impl_ppi_channel!(PPI20_CH11, DPPIC20, 11 => configurable); impl_ppi_channel!(PPI20_CH12, DPPIC20, 12 => configurable); impl_ppi_channel!(PPI20_CH13, DPPIC20, 13 => configurable); impl_ppi_channel!(PPI20_CH14, DPPIC20, 14 => configurable); impl_ppi_channel!(PPI20_CH15, DPPIC20, 15 => configurable); // DPPI30 channels impl_ppi_channel!(PPI30_CH0, DPPIC30, 0 => configurable); impl_ppi_channel!(PPI30_CH1, DPPIC30, 1 => configurable); impl_ppi_channel!(PPI30_CH2, DPPIC30, 2 => configurable); impl_ppi_channel!(PPI30_CH3, DPPIC30, 3 => configurable); // DPPI00 groups impl_ppi_group!(PPI00_GROUP0, DPPIC00, 0); impl_ppi_group!(PPI00_GROUP1, DPPIC00, 1); // DPPI10 groups impl_ppi_group!(PPI10_GROUP0, DPPIC10, 0); // DPPI20 groups impl_ppi_group!(PPI20_GROUP0, DPPIC20, 0); impl_ppi_group!(PPI20_GROUP1, DPPIC20, 1); impl_ppi_group!(PPI20_GROUP2, DPPIC20, 2); impl_ppi_group!(PPI20_GROUP3, DPPIC20, 3); impl_ppi_group!(PPI20_GROUP4, DPPIC20, 4); impl_ppi_group!(PPI20_GROUP5, DPPIC20, 5); // DPPI30 groups impl_ppi_group!(PPI30_GROUP0, DPPIC30, 0); impl_ppi_group!(PPI30_GROUP1, DPPIC30, 1); impl_timer!(TIMER00, TIMER00, TIMER00); impl_timer!(TIMER10, TIMER10, TIMER10); impl_timer!(TIMER20, TIMER20, TIMER20); impl_timer!(TIMER21, TIMER21, TIMER21); impl_timer!(TIMER22, TIMER22, TIMER22); impl_timer!(TIMER23, TIMER23, TIMER23); impl_timer!(TIMER24, TIMER24, TIMER24); impl_twim!(SERIAL20, TWIM20, SERIAL20); impl_twim!(SERIAL21, TWIM21, SERIAL21); impl_twim!(SERIAL22, TWIM22, SERIAL22); impl_twim!(SERIAL30, TWIM30, SERIAL30); impl_twis!(SERIAL20, TWIS20, SERIAL20); impl_twis!(SERIAL21, TWIS21, SERIAL21); impl_twis!(SERIAL22, TWIS22, SERIAL22); impl_twis!(SERIAL30, TWIS30, SERIAL30); impl_pwm!(PWM20, PWM20, PWM20); impl_pwm!(PWM21, PWM21, PWM21); impl_pwm!(PWM22, PWM22, PWM22); #[cfg(feature = "_s")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_S.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); #[cfg(feature = "_ns")] impl_spim!( SERIAL00, SPIM00, SERIAL00, match pac::OSCILLATORS_NS.pll().currentfreq().read().currentfreq() { pac::oscillators::vals::Currentfreq::CK128M => 128_000_000, pac::oscillators::vals::Currentfreq::CK64M => 64_000_000, _ => unreachable!(), } ); impl_spim!(SERIAL20, SPIM20, SERIAL20, 16_000_000); impl_spim!(SERIAL21, SPIM21, SERIAL21, 16_000_000); impl_spim!(SERIAL22, SPIM22, SERIAL22, 16_000_000); impl_spim!(SERIAL30, SPIM30, SERIAL30, 16_000_000); impl_spis!(SERIAL20, SPIS20, SERIAL20); impl_spis!(SERIAL21, SPIS21, SERIAL21); impl_spis!(SERIAL22, SPIS22, SERIAL22); impl_spis!(SERIAL30, SPIS30, SERIAL30); impl_uarte!(SERIAL00, UARTE00, SERIAL00); impl_uarte!(SERIAL20, UARTE20, SERIAL20); impl_uarte!(SERIAL21, UARTE21, SERIAL21); impl_uarte!(SERIAL22, UARTE22, SERIAL22); impl_uarte!(SERIAL30, UARTE30, SERIAL30); // NB: SAADC uses "pin" abstraction, not "AIN" impl_saadc_input!(P1_04, 1, 4); impl_saadc_input!(P1_05, 1, 5); impl_saadc_input!(P1_06, 1, 6); impl_saadc_input!(P1_07, 1, 7); impl_saadc_input!(P1_11, 1, 11); impl_saadc_input!(P1_12, 1, 12); impl_saadc_input!(P1_13, 1, 13); impl_saadc_input!(P1_14, 1, 14); #[cfg(feature = "_s")] impl_cracen!(CRACEN, CRACEN, CRACEN); embassy_hal_internal::interrupt_mod!( SWI00, SWI01, SWI02, SWI03, SPU00, MPC00, AAR00_CCM00, ECB00, CRACEN, SERIAL00, RRAMC, VPR00, CTRLAP, TIMER00, SPU10, TIMER10, 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, QDEC20, QDEC21, GRTC_0, GRTC_1, GRTC_2, GRTC_3, SPU30, SERIAL30, COMP_LPCOMP, WDT30, WDT31, GPIOTE30_0, GPIOTE30_1, CLOCK_POWER, ); ================================================ FILE: embassy-nrf/src/chips/nrf9120.rs ================================================ /// 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, CC_RNG_S as CC_RNG, 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, #[cfg(not(feature = "time-driver-rtc1"))] 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, // CryptoCell RNG #[cfg(feature = "_s")] CC_RNG } 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); #[cfg(feature = "_s")] impl_ccrng!(CC_RNG, CC_RNG, CRYPTOCELL); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); impl_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, DPPIC, 0 => configurable); impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); impl_ppi_group!(PPI_GROUP0, DPPIC, 0); impl_ppi_group!(PPI_GROUP1, DPPIC, 1); impl_ppi_group!(PPI_GROUP2, DPPIC, 2); impl_ppi_group!(PPI_GROUP3, DPPIC, 3); impl_ppi_group!(PPI_GROUP4, DPPIC, 4); impl_ppi_group!(PPI_GROUP5, DPPIC, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/chips/nrf9160.rs ================================================ /// 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, CC_RNG_S as CC_RNG, 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, #[cfg(not(feature = "time-driver-rtc1"))] 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, // CryptoCell RNG #[cfg(feature = "_s")] CC_RNG } 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); #[cfg(feature = "_s")] impl_ccrng!(CC_RNG, CC_RNG, CRYPTOCELL); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); impl_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); 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, DPPIC, 0 => configurable); impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); impl_ppi_group!(PPI_GROUP0, DPPIC, 0); impl_ppi_group!(PPI_GROUP1, DPPIC, 1); impl_ppi_group!(PPI_GROUP2, DPPIC, 2); impl_ppi_group!(PPI_GROUP3, DPPIC, 3); impl_ppi_group!(PPI_GROUP4, DPPIC, 4); impl_ppi_group!(PPI_GROUP5, DPPIC, 5); 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); impl_wdt!(WDT, WDT, WDT, 0); 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, ); ================================================ FILE: embassy-nrf/src/cracen.rs ================================================ //! CRACEN - Cryptographic Accelerator Engine driver. #![macro_use] use core::marker::PhantomData; use crate::mode::{Blocking, Mode}; use crate::{Peri, interrupt, pac, peripherals}; /// A wrapper around an nRF54 CRACEN peripheral. /// /// It has a blocking api through `rand`. pub struct Cracen<'d, M: Mode> { _peri: Peri<'d, peripherals::CRACEN>, _p: PhantomData, } impl<'d> Cracen<'d, Blocking> { /// Create a new CRACEN driver. pub fn new_blocking(_peri: Peri<'d, peripherals::CRACEN>) -> Self { Self { _peri, _p: PhantomData } } } impl<'d, M: Mode> Cracen<'d, M> { fn regs() -> pac::cracen::Cracen { pac::CRACEN } fn core() -> pac::cracencore::Cracencore { pac::CRACENCORE } fn start_rng(&self) { let r = Self::regs(); r.enable().write(|w| { w.set_rng(true); }); let r = Self::core(); r.rngcontrol().control().write(|w| { w.set_enable(true); }); while r.rngcontrol().status().read().state() == pac::cracencore::vals::State::STARTUP {} } fn stop_rng(&self) { let r = Self::core(); r.rngcontrol().control().write(|w| { w.set_enable(false); }); while r.rngcontrol().status().read().state() != pac::cracencore::vals::State::RESET {} let r = Self::regs(); r.enable().write(|w| { w.set_cryptomaster(false); w.set_rng(false); w.set_pkeikg(false); }); } /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { self.start_rng(); let r = Self::core(); for chunk in dest.chunks_mut(4) { while r.rngcontrol().fifolevel().read() == 0 {} let word = r.rngcontrol().fifo(0).read().to_ne_bytes(); let to_copy = word.len().min(chunk.len()); chunk[..to_copy].copy_from_slice(&word[..to_copy]); } self.stop_rng(); } /// Generate a random u32 pub fn blocking_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) } /// Generate a random u64 pub fn blocking_next_u64(&mut self) -> u64 { let mut bytes = [0; 8]; self.blocking_fill_bytes(&mut bytes); u64::from_ne_bytes(bytes) } } impl<'d, M: Mode> Drop for Cracen<'d, M> { fn drop(&mut self) { // nothing to do here, since we stop+disable rng for each operation. } } impl<'d, M: Mode> rand_core_06::RngCore for Cracen<'d, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } fn next_u32(&mut self) -> u32 { self.blocking_next_u32() } fn next_u64(&mut self) -> u64 { self.blocking_next_u64() } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { self.blocking_fill_bytes(dest); Ok(()) } } impl<'d, M: Mode> rand_core_06::CryptoRng for Cracen<'d, M> {} impl<'d, M: Mode> rand_core_09::RngCore for Cracen<'d, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } fn next_u32(&mut self) -> u32 { self.blocking_next_u32() } fn next_u64(&mut self) -> u64 { self.blocking_next_u64() } } impl<'d, M: Mode> rand_core_09::CryptoRng for Cracen<'d, M> {} pub(crate) trait SealedInstance {} /// CRACEN peripheral instance. #[allow(private_bounds)] pub trait Instance: SealedInstance + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_cracen { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::cracen::SealedInstance for peripherals::$type {} impl crate::cracen::Instance for peripherals::$type { type Interrupt = crate::interrupt::typelevel::$irq; } }; } ================================================ FILE: embassy-nrf/src/cryptocell_rng.rs ================================================ //! 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}; #[cfg(feature = "_nrf5340-app")] use embassy_futures::{select::select, yield_now}; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::WakerRegistration; use crate::interrupt::typelevel::Interrupt; use crate::mode::{Async, Blocking, Mode}; use crate::{interrupt, pac}; /// 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.rng_icr().write(|w| w.set_ehr_valid_clear(true)); pac::CC_HOST_RGF.icr().write(|w| w.set_rng_clear(true)); // 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 `CcRng::new` means that the future can't have been dropped // without calling its destructor. for i in 0..6 { let bytes = r.ehr_data(i).read().to_ne_bytes(); for b in bytes { unsafe { *state.ptr = b; state.ptr = state.ptr.add(1); } if state.ptr == state.end { state.waker.wake(); return; } } } } }); } } /// A wrapper around an nRF CryptoCell RNG peripheral. /// /// It has a non-blocking API, and a blocking api through `rand`. pub struct CcRng<'d, M: Mode> { r: pac::cc_rng::CcRng, state: &'static State, _phantom: PhantomData<(&'d (), M)>, } impl<'d> CcRng<'d, Blocking> { /// Creates a new RNG driver from the `CC_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_blocking(_rng: Peri<'d, T>) -> Self { let this = Self { r: T::regs(), state: T::state(), _phantom: PhantomData, }; this.stop(); this } } impl<'d> CcRng<'d, Async> { /// Creates a new RNG driver from the `CC_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: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { let this = Self { r: T::regs(), state: T::state(), _phantom: PhantomData, }; this.disable_irq(); this.stop(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; this } fn enable_irq(&self) { pac::CC_HOST_RGF .imr() .modify(|w| w.set_rng_mask(pac::cc_host_rgf::vals::RngMask::IRQENABLE)); self.r .rng_imr() .modify(|w| w.set_ehr_valid_mask(pac::cc_rng::vals::EhrValidMask::IRQENABLE)); } fn disable_irq(&self) { self.r.rng_icr().write(|w| w.set_ehr_valid_clear(true)); pac::CC_HOST_RGF.icr().write(|w| w.set_rng_clear(true)); self.r .rng_imr() .modify(|w| w.set_ehr_valid_mask(pac::cc_rng::vals::EhrValidMask::IRQDISABLE)); pac::CC_HOST_RGF .imr() .modify(|w| w.set_rng_mask(pac::cc_host_rgf::vals::RngMask::IRQDISABLE)); } /// 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(); let state = self.state; // 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 = state.borrow_mut(cs); state.ptr = range.start; state.end = range.end; }); // In self.start() there are calls to set_enable() that resets the interrupt mask, // self.enable_irq() needs to be called after self.start(). self.start(); self.enable_irq(); let on_drop = OnDrop::new(|| { self.disable_irq(); self.stop(); critical_section::with(|cs| { let mut state = state.borrow_mut(cs); state.ptr = ptr::null_mut(); state.end = ptr::null_mut(); }); }); let fill_future = poll_fn(|cx| { critical_section::with(|cs| { let mut s = state.borrow_mut(cs); s.waker.register(cx.waker()); if s.ptr == s.end { // We're done. Poll::Ready(()) } else { Poll::Pending } }) }); // nrf5340 needs to be reading from the CryptoCell in order to receive an interrupt from it. #[cfg(feature = "_nrf5340-app")] let _ = select(fill_future, async { loop { let _ = pac::CRYPTOCELL.enable().read().enable(); yield_now().await; } }) .await; #[cfg(not(feature = "_nrf5340-app"))] fill_future.await; // Trigger the teardown drop(on_drop); } } impl<'d, M: Mode> CcRng<'d, M> { fn start(&self) { // FIXME: CRYPTOCELL is never disabled. if !pac::CRYPTOCELL.enable().read().enable() { pac::CRYPTOCELL.enable().write(|w| w.set_enable(true)); } self.r.rng_clk().write(|w| w.set_enable(true)); self.r.rng_sw_reset().write(|w| w.set_reset(true)); // Wait for calibration // ROSC1 (ring oscillator lenght) chosen arbitrarly, can be later exposed as configuration. loop { self.r.rng_clk().write(|w| w.set_enable(true)); self.r.sample_cnt().write_value(pac::FICR.trng90b().rosc1().read()); if self.r.sample_cnt().read() == pac::FICR.trng90b().rosc1().read() { break; }; } self.r .trng_config() .modify(|w| w.set_rosc_len(pac::cc_rng::vals::TrngConfigRoscLen::ROSC1)); self.r.noise_source().modify(|w| w.set_enable(true)); } fn stop(&self) { self.r.noise_source().modify(|w| w.set_enable(false)); self.r.rng_clk().write(|w| w.set_enable(false)); } /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { self.start(); self.inner_fill_bytes(dest); self.stop(); } // inner function so we can use `return` to end all the loops fn inner_fill_bytes(&mut self, dest: &mut [u8]) { let mut index = 0; while index < dest.len() { while !self.r.rng_isr().read().ehr_valid_int() {} self.r.rng_icr().write(|w| w.set_ehr_valid_clear(true)); for i in 0..6 { let bytes = self.r.ehr_data(i).read().to_ne_bytes(); for b in bytes { dest[index] = b; index += 1; if index >= dest.len() { return; } } } } } /// Generate a random u32 pub fn blocking_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) } /// Generate a random u64 pub fn blocking_next_u64(&mut self) -> u64 { let mut bytes = [0; 8]; self.blocking_fill_bytes(&mut bytes); u64::from_ne_bytes(bytes) } } impl<'d, M: Mode> Drop for CcRng<'d, M> { fn drop(&mut self) { self.stop(); critical_section::with(|cs| { let mut state = self.state.borrow_mut(cs); state.ptr = ptr::null_mut(); state.end = ptr::null_mut(); }); } } impl<'d, M: Mode> rand_core_06::RngCore for CcRng<'d, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } fn next_u32(&mut self) -> u32 { self.blocking_next_u32() } fn next_u64(&mut self) -> u64 { self.blocking_next_u64() } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { self.blocking_fill_bytes(dest); Ok(()) } } impl<'d, M: Mode> rand_core_06::CryptoRng for CcRng<'d, M> {} impl<'d, M: Mode> rand_core_09::RngCore for CcRng<'d, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } fn next_u32(&mut self) -> u32 { self.blocking_next_u32() } fn next_u64(&mut self) -> u64 { self.blocking_next_u64() } } impl<'d, M: Mode> rand_core_09::CryptoRng for CcRng<'d, M> {} /// 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::cc_rng::CcRng; fn state() -> &'static State; } /// RNG peripheral instance. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_ccrng { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::cryptocell_rng::SealedInstance for peripherals::$type { fn regs() -> pac::cc_rng::CcRng { pac::$pac_type } fn state() -> &'static crate::cryptocell_rng::State { static STATE: crate::cryptocell_rng::State = crate::cryptocell_rng::State::new(); &STATE } } impl crate::cryptocell_rng::Instance for peripherals::$type { type Interrupt = crate::interrupt::typelevel::$irq; } }; } ================================================ FILE: embassy-nrf/src/egu.rs ================================================ //! 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::PeripheralType; use crate::ppi::{Event, Task}; use crate::{Peri, interrupt, pac}; /// An instance of the EGU. pub struct Egu<'d> { r: pac::egu::Egu, _phantom: PhantomData<&'d ()>, } impl<'d> Egu<'d> { /// Create a new EGU instance. pub fn new(_p: Peri<'d, T>) -> Self { Self { r: T::regs(), _phantom: PhantomData, } } /// Get a handle to a trigger for the EGU. pub fn trigger(&mut self, number: TriggerNumber) -> Trigger<'d> { Trigger { number, r: self.r, _phantom: PhantomData, } } } pub(crate) trait SealedInstance { fn regs() -> pac::egu::Egu; } /// Basic Egu instance. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + '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> { number: TriggerNumber, r: pac::egu::Egu, _phantom: PhantomData<&'d ()>, } impl<'d> Trigger<'d> { /// Get task for this trigger to use with PPI. pub fn task(&self) -> Task<'d> { let nr = self.number as usize; Task::from_reg(self.r.tasks_trigger(nr)) } /// Get event for this trigger to use with PPI. pub fn event(&self) -> Event<'d> { let nr = self.number as usize; Event::from_reg(self.r.events_triggered(nr)) } /// Enable interrupts for this trigger pub fn enable_interrupt(&mut self) { self.r .intenset() .modify(|w| w.set_triggered(self.number as usize, true)); } /// Enable interrupts for this trigger pub fn disable_interrupt(&mut self) { self.r .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, } ================================================ FILE: embassy-nrf/src/embassy_net_802154_driver.rs ================================================ //! embassy-net IEEE 802.15.4 driver use embassy_futures::select::{Either3, select3}; use embassy_net_driver_channel::driver::LinkState; use embassy_net_driver_channel::{self as ch}; use embassy_time::{Duration, Ticker}; use crate::radio::InterruptHandler; use crate::radio::ieee802154::{Packet, Radio}; use crate::{self as nrf, interrupt}; /// MTU for the nrf radio. pub const MTU: usize = Packet::CAPACITY as usize; /// embassy-net device for the driver. pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; /// Internal state for the embassy-net driver. 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> { radio: nrf::radio::ieee802154::Radio<'d>, ch: ch::Runner<'d, MTU>, } impl<'d> Runner<'d> { /// Drives the radio. Needs to run to use 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)); let mut packet = Packet::new(); state_chan.set_link_state(LinkState::Up); loop { match select3( async { let rx_buf = rx_chan.rx_buf().await; self.radio.receive(&mut packet).await.ok().map(|_| rx_buf) }, tx_chan.tx_buf(), tick.next(), ) .await { Either3::First(Some(rx_buf)) => { let len = rx_buf.len().min(packet.len() as usize); (&mut rx_buf[..len]).copy_from_slice(&*packet); rx_chan.rx_done(len); } Either3::Second(tx_buf) => { let len = tx_buf.len().min(Packet::CAPACITY as usize); packet.copy_from_slice(&tx_buf[..len]); self.radio.try_send(&mut packet).await.ok().unwrap(); tx_chan.tx_done(); } _ => {} } } } } /// Make sure to use `HfclkSource::ExternalXtal` as the `hfclk_source` /// to use the radio (nrf52840 product spec v1.11 5.4.1) /// ``` /// # use embassy_nrf::config::*; /// let mut config = Config::default(); /// config.hfclk_source = HfclkSource::ExternalXtal; /// ``` pub async fn new<'a, const N_RX: usize, const N_TX: usize, T: nrf::radio::Instance, Irq>( mac_addr: [u8; 8], radio: nrf::Peri<'a, T>, irq: Irq, state: &'a mut State, ) -> Result<(Device<'a>, Runner<'a>), ()> where Irq: interrupt::typelevel::Binding> + 'a, { let radio = Radio::new(radio, irq); let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ieee802154(mac_addr)); Ok((device, Runner { ch: runner, radio })) } ================================================ FILE: embassy-nrf/src/fmt.rs ================================================ #![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) } } ================================================ FILE: embassy-nrf/src/gpio.rs ================================================ //! 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::{Peri, PeripheralType, impl_peripheral}; use crate::pac; use crate::pac::common::{RW, Reg}; use crate::pac::gpio; use crate::pac::gpio::vals; #[cfg(not(feature = "_nrf51"))] use crate::pac::shared::{regs::Psel, vals::Connect}; /// 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(Clone, Copy, 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: Peri<'d, impl Pin>, 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() } } impl Input<'static> { /// Persist the pin's configuration for the rest of the program's lifetime. This method should /// be preferred over [`core::mem::forget()`] because the `'static` bound prevents accidental /// reuse of the underlying peripheral. pub fn persist(self) { self.pin.persist() } } /// 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 [OutputDrive] configuration. #[inline] pub fn new(pin: Peri<'d, impl Pin>, 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() } } impl Output<'static> { /// Persist the pin's configuration for the rest of the program's lifetime. This method should /// be preferred over [`core::mem::forget()`] because the `'static` bound prevents accidental /// reuse of the underlying peripheral. pub fn persist(self) { self.pin.persist() } } 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_drive1(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: Peri<'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: Peri<'d, impl Pin>) -> Self { // Pin will be in disconnected state. Self { pin: pin.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(|w| { w.set_input(vals::Input::DISCONNECT); }); } /// 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 Flex<'static> { /// Persist the pin's configuration for the rest of the program's lifetime. This method should /// be preferred over [`core::mem::forget()`] because the `'static` bound prevents accidental /// reuse of the underlying peripheral. pub fn persist(self) { core::mem::forget(self); } } impl<'d> Drop for Flex<'d> { fn drop(&mut self) { self.set_as_disconnected(); } } 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: PeripheralType + 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) } } /// Type-erased GPIO pin pub struct AnyPin { pub(crate) 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) -> Peri<'static, Self> { Peri::new_unchecked(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"))] 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"))] 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(|w| { w.set_input(vals::Input::DISCONNECT); }) } // ==================== 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 { Self { pin_port: $port_num * 32 + $pin_num, } } } }; } // ==================== 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 [embedded_hal_1::digital::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()) } } ================================================ FILE: embassy-nrf/src/gpiote.rs ================================================ //! GPIO task/event (GPIOTE) driver. #![macro_use] use core::convert::Infallible; use core::future::{Future, poll_fn}; use core::task::{Context, Poll}; use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::{AnyPin, Flex, Input, Level, Output, OutputDrive, Pin as GpioPin, Pull, 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(any(feature = "_nrf51", feature = "_nrf54l")))] /// Amount of GPIOTE channels in the chip. const CHANNEL_COUNT: usize = 8; #[cfg(any(feature = "_nrf54l"))] /// Amount of GPIOTE channels in the chip. const CHANNEL_COUNT: usize = 12; /// Max channels per port const CHANNELS_PER_PORT: usize = 8; #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] const PIN_COUNT: usize = 48; #[cfg(any(feature = "_nrf54l15", feature = "_nrf54l10", feature = "_nrf54l05"))] // Highest pin index is P2.10 => (2 * 32 + 10) = 74 const PIN_COUNT: usize = 75; #[cfg(feature = "_nrf54lm20")] // Highest pin index is P3.12 => (3 * 32 + 12) = 108 const PIN_COUNT: usize = 109; #[cfg(not(any( feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340", feature = "_nrf54l", )))] 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, } 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 #[cfg(all(feature = "_s", not(feature = "_nrf54l")))] p.detectmode_sec().write(|w| w.set_detectmode(Detectmode::LDETECT)); #[cfg(any(not(feature = "_s"), all(feature = "_s", feature = "_nrf54l")))] 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 irqs = &[(pac::GPIOTE0, interrupt::GPIOTE0)]; #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns", feature = "nrf9120-ns"))] let irqs = &[(pac::GPIOTE1, interrupt::GPIOTE1)]; #[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] let irqs = &[(pac::GPIOTE, interrupt::GPIOTE)]; #[cfg(any(feature = "_nrf54l"))] let irqs = &[ #[cfg(feature = "_s")] (pac::GPIOTE20, interrupt::GPIOTE20_0), #[cfg(feature = "_s")] (pac::GPIOTE30, interrupt::GPIOTE30_0), #[cfg(feature = "_ns")] (pac::GPIOTE20, interrupt::GPIOTE20_1), #[cfg(feature = "_ns")] (pac::GPIOTE30, interrupt::GPIOTE30_1), ]; for (inst, irq) in irqs { irq.unpend(); irq.set_priority(irq_prio); unsafe { irq.enable() }; let g = inst; #[cfg(not(feature = "_nrf54l"))] g.intenset(INTNUM).write(|w| w.set_port(true)); #[cfg(all(feature = "_nrf54l", feature = "_ns"))] g.intenset(INTNUM).write(|w| w.set_port0nonsecure(true)); #[cfg(all(feature = "_nrf54l", feature = "_s"))] g.intenset(INTNUM).write(|w| w.set_port0secure(true)); } } #[cfg(all(feature = "_nrf54l", feature = "_ns"))] const INTNUM: usize = 1; #[cfg(any(not(feature = "_nrf54l"), feature = "_s"))] const INTNUM: usize = 0; #[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s", feature = "nrf9120-s"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE0() { unsafe { handle_gpiote_interrupt(pac::GPIOTE0) }; } #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns", feature = "nrf9120-ns"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE1() { unsafe { handle_gpiote_interrupt(pac::GPIOTE1) }; } #[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE() { unsafe { handle_gpiote_interrupt(pac::GPIOTE) }; } #[cfg(all(feature = "_nrf54l", feature = "_s"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE20_0() { unsafe { handle_gpiote_interrupt(pac::GPIOTE20) }; } #[cfg(all(feature = "_nrf54l", feature = "_s"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE30_0() { unsafe { handle_gpiote_interrupt(pac::GPIOTE30) }; } #[cfg(all(feature = "_nrf54l", feature = "_ns"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE20_1() { unsafe { handle_gpiote_interrupt(pac::GPIOTE20) }; } #[cfg(all(feature = "_nrf54l", feature = "_ns"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE30_1() { unsafe { handle_gpiote_interrupt(pac::GPIOTE30) }; } unsafe fn handle_gpiote_interrupt(g: pac::gpiote::Gpiote) { for c in 0..CHANNEL_COUNT { let i = c % CHANNELS_PER_PORT; if g.events_in(i).read() != 0 { g.intenclr(INTNUM).write(|w| w.0 = 1 << i); CHANNEL_WAKERS[c].wake(); } } #[cfg(not(feature = "_nrf54l"))] let eport = g.events_port(0); #[cfg(all(feature = "_nrf54l", feature = "_ns"))] let eport = g.events_port(0).nonsecure(); #[cfg(all(feature = "_nrf54l", feature = "_s"))] let eport = g.events_port(0).secure(); if eport.read() != 0 { eport.write_value(0); #[cfg(any( feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340", feature = "_nrf54l" ))] let ports = &[pac::P0, pac::P1]; #[cfg(not(any( feature = "_nrf51", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340", feature = "_nrf54l" )))] 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: Peri<'d, AnyChannel>, pin: Input<'d>, } impl InputChannel<'static> { /// Persist the channel's configuration for the rest of the program's lifetime. This method /// should be preferred over [`core::mem::forget()`] because the `'static` bound prevents /// accidental reuse of the underlying peripheral. pub fn persist(self) { core::mem::forget(self); } } impl<'d> Drop for InputChannel<'d> { fn drop(&mut self) { let g = self.ch.regs(); let num = self.ch.number(); g.config(num).write(|w| w.set_mode(Mode::DISABLED)); g.intenclr(INTNUM).write(|w| w.0 = 1 << num); } } impl<'d> InputChannel<'d> { /// Create a new GPIOTE input channel driver. #[cfg(feature = "_nrf54l")] pub fn new>( ch: Peri<'d, C>, pin: Peri<'d, T>, pull: Pull, polarity: InputChannelPolarity, ) -> Self { let pin = Input::new(pin, pull); let ch = ch.into(); Self::new_inner(ch, pin, polarity) } /// Create a new GPIOTE output channel driver. #[cfg(not(feature = "_nrf54l"))] pub fn new( ch: Peri<'d, C>, pin: Peri<'d, T>, pull: Pull, polarity: InputChannelPolarity, ) -> Self { let pin = Input::new(pin, pull); let ch = ch.into(); Self::new_inner(ch, pin, polarity) } fn new_inner(ch: Peri<'d, AnyChannel>, pin: Input<'d>, polarity: InputChannelPolarity) -> Self { let g = ch.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, }); #[cfg(any(feature = "_nrf54l"))] w.set_port(match pin.pin.pin.port() { crate::gpio::Port::Port0 => 0, crate::gpio::Port::Port1 => 1, crate::gpio::Port::Port2 => 2, }); w.set_psel(pin.pin.pin.pin()); }); g.events_in(num).write_value(0); InputChannel { ch, pin } } /// Asynchronously wait for an event in this channel. /// /// It is possible to call this function and await the returned future later. /// If an even occurs in the mean time, the future will immediately report ready. pub fn wait(&mut self) -> impl Future { // NOTE: This is `-> impl Future` and not an `async fn` on purpose. // Otherwise, events will only be detected starting at the first poll of the returned future. Self::wait_internal(&mut self.ch) } /// Asynchronously wait for the pin to become high. /// /// The channel must be configured with [`InputChannelPolarity::LoToHi`] or [`InputChannelPolarity::Toggle`]. /// If the channel is not configured to detect rising edges, it is unspecified when the returned future completes. /// /// It is possible to call this function and await the returned future later. /// If an even occurs in the mean time, the future will immediately report ready. pub fn wait_for_high(&mut self) -> impl Future { // NOTE: This is `-> impl Future` and not an `async fn` on purpose. // Otherwise, events will only be detected starting at the first poll of the returned future. // Subscribe to the event before checking the pin level. let wait = Self::wait_internal(&mut self.ch); let pin = &self.pin; async move { if pin.is_high() { return; } wait.await; } } /// Asynchronously wait for the pin to become low. /// /// The channel must be configured with [`InputChannelPolarity::HiToLo`] or [`InputChannelPolarity::Toggle`]. /// If the channel is not configured to detect falling edges, it is unspecified when the returned future completes. /// /// It is possible to call this function and await the returned future later. /// If an even occurs in the mean time, the future will immediately report ready. pub fn wait_for_low(&mut self) -> impl Future { // NOTE: This is `-> impl Future` and not an `async fn` on purpose. // Otherwise, events will only be detected starting at the first poll of the returned future. // Subscribe to the event before checking the pin level. let wait = Self::wait_internal(&mut self.ch); let pin = &self.pin; async move { if pin.is_low() { return; } wait.await; } } /// Internal implementation for `wait()` and friends. fn wait_internal(channel: &mut Peri<'_, AnyChannel>) -> impl Future { // NOTE: This is `-> impl Future` and not an `async fn` on purpose. // Otherwise, events will only be detected starting at the first poll of the returned future. let g = channel.regs(); let num = channel.number(); let waker = channel.waker(); // Enable interrupt g.events_in(num).write_value(0); g.intenset(INTNUM).write(|w| w.0 = 1 << num); poll_fn(move |cx| { CHANNEL_WAKERS[waker].register(cx.waker()); if g.events_in(num).read() != 0 { Poll::Ready(()) } else { Poll::Pending } }) } /// Get the associated input pin. pub fn pin(&self) -> &Input<'_> { &self.pin } /// Returns the IN event, for use with PPI. pub fn event_in(&self) -> Event<'d> { let g = self.ch.regs(); Event::from_reg(g.events_in(self.ch.number())) } } /// GPIOTE channel driver in output mode pub struct OutputChannel<'d> { ch: Peri<'d, AnyChannel>, _pin: Output<'d>, } impl OutputChannel<'static> { /// Persist the channel's configuration for the rest of the program's lifetime. This method /// should be preferred over [`core::mem::forget()`] because the `'static` bound prevents /// accidental reuse of the underlying peripheral. pub fn persist(self) { core::mem::forget(self); } } impl<'d> Drop for OutputChannel<'d> { fn drop(&mut self) { let g = self.ch.regs(); let num = self.ch.number(); g.config(num).write(|w| w.set_mode(Mode::DISABLED)); g.intenclr(INTNUM).write(|w| w.0 = 1 << num); } } impl<'d> OutputChannel<'d> { /// Create a new GPIOTE output channel driver. #[cfg(feature = "_nrf54l")] pub fn new>( ch: Peri<'d, C>, pin: Peri<'d, T>, initial_output: Level, drive: OutputDrive, polarity: OutputChannelPolarity, ) -> Self { let pin = Output::new(pin, initial_output, drive); let ch = ch.into(); Self::new_inner(ch, pin, polarity) } /// Create a new GPIOTE output channel driver. #[cfg(not(feature = "_nrf54l"))] pub fn new( ch: Peri<'d, C>, pin: Peri<'d, T>, initial_output: Level, drive: OutputDrive, polarity: OutputChannelPolarity, ) -> Self { let pin = Output::new(pin, initial_output, drive); let ch = ch.into(); Self::new_inner(ch, pin, polarity) } fn new_inner(ch: Peri<'d, AnyChannel>, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { let g = ch.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, }); #[cfg(any(feature = "_nrf54l"))] w.set_port(match pin.pin.pin.port() { crate::gpio::Port::Port0 => 0, crate::gpio::Port::Port1 => 1, crate::gpio::Port::Port2 => 2, }); w.set_psel(pin.pin.pin.pin()); }); OutputChannel { ch, _pin: pin } } /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). pub fn out(&self) { let g = self.ch.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 = self.ch.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 = self.ch.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 = self.ch.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 = self.ch.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 = self.ch.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: Peri<'a, AnyPin>, } impl<'a> PortInputFuture<'a> { fn new(pin: Peri<'a, impl GpioPin>) -> Self { Self { pin: pin.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(self.pin.reborrow()).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(self.pin.reborrow()).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(self.pin.reborrow()).await } } // ======================= // trait SealedChannel { fn waker(&self) -> usize; fn regs(&self) -> pac::gpiote::Gpiote; } /// GPIOTE channel trait. /// /// Implemented by all GPIOTE channels. #[allow(private_bounds)] pub trait Channel: PeripheralType + SealedChannel + Into + Sized + 'static { #[cfg(feature = "_nrf54l")] /// GPIOTE instance this channel belongs to. type Instance: GpioteInstance; /// Get the channel number. fn number(&self) -> usize; } struct AnyChannel { number: u8, regs: pac::gpiote::Gpiote, waker: u8, } impl_peripheral!(AnyChannel); impl SealedChannel for AnyChannel { fn waker(&self) -> usize { self.waker as usize } fn regs(&self) -> pac::gpiote::Gpiote { self.regs } } #[cfg(feature = "_nrf54l")] impl AnyChannel { fn number(&self) -> usize { self.number as usize } } #[cfg(not(feature = "_nrf54l"))] impl Channel for AnyChannel { fn number(&self) -> usize { self.number as usize } } macro_rules! impl_channel { ($type:ident, $inst:ident, $number:expr, $waker:expr) => { impl SealedChannel for peripherals::$type { fn waker(&self) -> usize { $waker as usize } fn regs(&self) -> pac::gpiote::Gpiote { pac::$inst } } impl Channel for peripherals::$type { #[cfg(feature = "_nrf54l")] type Instance = peripherals::$inst; fn number(&self) -> usize { $number as usize } } impl From for AnyChannel { fn from(val: peripherals::$type) -> Self { Self { number: val.number() as u8, waker: val.waker() as u8, regs: val.regs(), } } } }; } cfg_if::cfg_if! { if #[cfg(feature = "_nrf54l")] { trait SealedGpioteInstance {} /// Represents a GPIOTE instance. #[allow(private_bounds)] pub trait GpioteInstance: PeripheralType + SealedGpioteInstance + Sized + 'static {} macro_rules! impl_gpiote { ($type:ident) => { impl SealedGpioteInstance for peripherals::$type {} impl GpioteInstance for peripherals::$type {} }; } pub(crate) trait SealedGpiotePin {} /// Represents a GPIO pin that can be used with GPIOTE. #[allow(private_bounds)] pub trait GpiotePin: GpioPin + SealedGpiotePin { /// The GPIOTE instance this pin belongs to. type Instance: GpioteInstance; } macro_rules! impl_gpiote_pin { ($type:ident, $inst:ident) => { impl crate::gpiote::SealedGpiotePin for peripherals::$type {} impl crate::gpiote::GpiotePin for peripherals::$type { type Instance = peripherals::$inst; } }; } impl_gpiote!(GPIOTE20); impl_gpiote!(GPIOTE30); impl_channel!(GPIOTE20_CH0, GPIOTE20, 0, 0); impl_channel!(GPIOTE20_CH1, GPIOTE20, 1, 1); impl_channel!(GPIOTE20_CH2, GPIOTE20, 2, 2); impl_channel!(GPIOTE20_CH3, GPIOTE20, 3, 3); impl_channel!(GPIOTE20_CH4, GPIOTE20, 4, 4); impl_channel!(GPIOTE20_CH5, GPIOTE20, 5, 5); impl_channel!(GPIOTE20_CH6, GPIOTE20, 6, 6); impl_channel!(GPIOTE20_CH7, GPIOTE20, 7, 7); impl_channel!(GPIOTE30_CH0, GPIOTE30, 0, 8); impl_channel!(GPIOTE30_CH1, GPIOTE30, 1, 9); impl_channel!(GPIOTE30_CH2, GPIOTE30, 2, 10); impl_channel!(GPIOTE30_CH3, GPIOTE30, 3, 11); } else if #[cfg(feature = "_nrf51")] { impl_channel!(GPIOTE_CH0, GPIOTE, 0, 0); impl_channel!(GPIOTE_CH1, GPIOTE, 1, 1); impl_channel!(GPIOTE_CH2, GPIOTE, 2, 2); impl_channel!(GPIOTE_CH3, GPIOTE, 3, 3); } else if #[cfg(all(feature = "_s", any(feature = "_nrf91", feature = "_nrf5340")))] { impl_channel!(GPIOTE_CH0, GPIOTE0, 0, 0); impl_channel!(GPIOTE_CH1, GPIOTE0, 1, 1); impl_channel!(GPIOTE_CH2, GPIOTE0, 2, 2); impl_channel!(GPIOTE_CH3, GPIOTE0, 3, 3); impl_channel!(GPIOTE_CH4, GPIOTE0, 4, 4); impl_channel!(GPIOTE_CH5, GPIOTE0, 5, 5); impl_channel!(GPIOTE_CH6, GPIOTE0, 6, 6); impl_channel!(GPIOTE_CH7, GPIOTE0, 7, 7); } else if #[cfg(all(feature = "_ns", any(feature = "_nrf91", feature = "_nrf5340")))] { impl_channel!(GPIOTE_CH0, GPIOTE1, 0, 0); impl_channel!(GPIOTE_CH1, GPIOTE1, 1, 1); impl_channel!(GPIOTE_CH2, GPIOTE1, 2, 2); impl_channel!(GPIOTE_CH3, GPIOTE1, 3, 3); impl_channel!(GPIOTE_CH4, GPIOTE1, 4, 4); impl_channel!(GPIOTE_CH5, GPIOTE1, 5, 5); impl_channel!(GPIOTE_CH6, GPIOTE1, 6, 6); impl_channel!(GPIOTE_CH7, GPIOTE1, 7, 7); } else { impl_channel!(GPIOTE_CH0, GPIOTE, 0, 0); impl_channel!(GPIOTE_CH1, GPIOTE, 1, 1); impl_channel!(GPIOTE_CH2, GPIOTE, 2, 2); impl_channel!(GPIOTE_CH3, GPIOTE, 3, 3); impl_channel!(GPIOTE_CH4, GPIOTE, 4, 4); impl_channel!(GPIOTE_CH5, GPIOTE, 5, 5); impl_channel!(GPIOTE_CH6, GPIOTE, 6, 6); impl_channel!(GPIOTE_CH7, GPIOTE, 7, 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) } } ================================================ FILE: embassy-nrf/src/i2s.rs ================================================ //! 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::{AtomicBool, Ordering, compiler_fence}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, PeripheralType}; 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::{EASY_DMA_SIZE, interrupt, pac}; /// 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 [vals::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(T::regs()); 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> { r: pac::i2s::I2s, state: &'static State, mck: Option>, sck: Peri<'d, AnyPin>, lrck: Peri<'d, AnyPin>, sdin: Option>, sdout: Option>, master_clock: Option, config: Config, } impl<'d> I2S<'d> { /// Create a new I2S in master mode pub fn new_master( _i2s: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, mck: Peri<'d, impl GpioPin>, sck: Peri<'d, impl GpioPin>, lrck: Peri<'d, impl GpioPin>, master_clock: MasterClock, config: Config, ) -> Self { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self { r: T::regs(), state: T::state(), mck: Some(mck.into()), sck: sck.into(), lrck: lrck.into(), sdin: None, sdout: None, master_clock: Some(master_clock), config, } } /// Create a new I2S in slave mode pub fn new_slave( _i2s: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, sck: Peri<'d, impl GpioPin>, lrck: Peri<'d, impl GpioPin>, config: Config, ) -> Self { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; Self { r: T::regs(), state: T::state(), mck: None, sck: sck.into(), lrck: lrck.into(), sdin: None, sdout: None, master_clock: None, config, } } /// I2S output only pub fn output( mut self, sdout: Peri<'d, impl GpioPin>, buffers: MultiBuffering, ) -> OutputStream<'d, S, NB, NS> { self.sdout = Some(sdout.into()); let p = self.build(); OutputStream { r: p.0, state: p.1, _phantom: PhantomData, buffers, } } /// I2S input only pub fn input( mut self, sdin: Peri<'d, impl GpioPin>, buffers: MultiBuffering, ) -> InputStream<'d, S, NB, NS> { self.sdin = Some(sdin.into()); let p = self.build(); InputStream { r: p.0, state: p.1, buffers, _phantom: PhantomData, } } /// I2S full duplex (input and output) pub fn full_duplex( mut self, sdin: Peri<'d, impl GpioPin>, sdout: Peri<'d, impl GpioPin>, buffers_out: MultiBuffering, buffers_in: MultiBuffering, ) -> FullDuplexStream<'d, S, NB, NS> { self.sdout = Some(sdout.into()); self.sdin = Some(sdin.into()); let p = self.build(); FullDuplexStream { r: p.0, state: p.1, _phantom: PhantomData, buffers_out, buffers_in, } } fn build(self) -> (pac::i2s::I2s, &'static State) { self.apply_config(); self.select_pins(); self.setup_interrupt(); let device = Device::new(self.r); device.enable(); (self.r, self.state) } fn apply_config(&self) { let c = self.r.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 = self.r.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) { // Interrupt is already set up in constructor let device = Device::new(self.r); 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(r: pac::i2s::I2s, state: &State) { compiler_fence(Ordering::SeqCst); let device = Device::new(r); device.stop(); state.started.store(false, Ordering::Relaxed); poll_fn(|cx| { 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(r: pac::i2s::I2s, state: &State, 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(r); device.update_tx(buffer_ptr)?; Self::wait_tx_ptr_update(r, state).await; compiler_fence(Ordering::SeqCst); Ok(()) } async fn wait_tx_ptr_update(r: pac::i2s::I2s, state: &State) { let drop = OnDrop::new(move || { trace!("TX DROP: Stopping"); let device = Device::new(r); 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| { state.tx_waker.register(cx.waker()); let device = Device::new(r); 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(r: pac::i2s::I2s, state: &State, 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(r); device.update_rx(buffer_ptr)?; Self::wait_rx_ptr_update(r, state).await; compiler_fence(Ordering::SeqCst); Ok(()) } async fn wait_rx_ptr_update(r: pac::i2s::I2s, state: &State) { let drop = OnDrop::new(move || { trace!("RX DROP: Stopping"); let device = Device::new(r); 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| { state.rx_waker.register(cx.waker()); let device = Device::new(r); 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, S: Sample, const NB: usize, const NS: usize> { r: pac::i2s::I2s, state: &'static State, buffers: MultiBuffering, _phantom: PhantomData<&'d ()>, } impl<'d, S: Sample, const NB: usize, const NS: usize> OutputStream<'d, 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(self.r); if self.state.started.load(Ordering::Relaxed) { self.stop().await; } device.enable(); device.enable_tx(); device.update_tx(self.buffers.switch())?; self.state.started.store(true, Ordering::Relaxed); device.start(); I2S::wait_tx_ptr_update(self.r, self.state).await; Ok(()) } /// Stops the I2S transfer and waits until it has stopped. #[inline(always)] pub async fn stop(&self) { I2S::stop(self.r, self.state).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.r, self.state, self.buffers.switch()).await } } /// I2S input pub struct InputStream<'d, S: Sample, const NB: usize, const NS: usize> { r: pac::i2s::I2s, state: &'static State, buffers: MultiBuffering, _phantom: PhantomData<&'d ()>, } impl<'d, S: Sample, const NB: usize, const NS: usize> InputStream<'d, 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(self.r); if self.state.started.load(Ordering::Relaxed) { self.stop().await; } device.enable(); device.enable_rx(); device.update_rx(self.buffers.switch())?; self.state.started.store(true, Ordering::Relaxed); device.start(); I2S::wait_rx_ptr_update(self.r, self.state).await; Ok(()) } /// Stops the I2S transfer and waits until it has stopped. #[inline(always)] pub async fn stop(&self) { I2S::stop(self.r, self.state).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.r, self.state, self.buffers.switch_mut()).await } } /// I2S full duplex stream (input & output) pub struct FullDuplexStream<'d, S: Sample, const NB: usize, const NS: usize> { r: pac::i2s::I2s, state: &'static State, buffers_out: MultiBuffering, buffers_in: MultiBuffering, _phantom: PhantomData<&'d ()>, } impl<'d, S: Sample, const NB: usize, const NS: usize> FullDuplexStream<'d, 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(self.r); if self.state.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())?; self.state.started.store(true, Ordering::Relaxed); device.start(); I2S::wait_tx_ptr_update(self.r, self.state).await; I2S::wait_rx_ptr_update(self.r, self.state).await; Ok(()) } /// Stops the I2S transfer and waits until it has stopped. #[inline(always)] pub async fn stop(&self) { I2S::stop(self.r, self.state).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.r, self.state, self.buffers_out.switch()).await?; I2S::receive_from_ram(self.r, self.state, self.buffers_in.switch_mut()).await?; Ok(()) } } /// Helper encapsulating common I2S device operations. struct Device(pac::i2s::I2s); impl Device { fn new(r: pac::i2s::I2s) -> Self { Self(r) } #[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: SealedInstance + PeripheralType + '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; } }; } ================================================ FILE: embassy-nrf/src/ipc.rs ================================================ //! InterProcessor Communication (IPC) #![macro_use] use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::interrupt::typelevel::Interrupt; use crate::{interrupt, pac, ppi}; const EVENT_COUNT: usize = 16; /// IPC Event #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EventNumber { /// IPC Event 0 Event0 = 0, /// IPC Event 1 Event1 = 1, /// IPC Event 2 Event2 = 2, /// IPC Event 3 Event3 = 3, /// IPC Event 4 Event4 = 4, /// IPC Event 5 Event5 = 5, /// IPC Event 6 Event6 = 6, /// IPC Event 7 Event7 = 7, /// IPC Event 8 Event8 = 8, /// IPC Event 9 Event9 = 9, /// IPC Event 10 Event10 = 10, /// IPC Event 11 Event11 = 11, /// IPC Event 12 Event12 = 12, /// IPC Event 13 Event13 = 13, /// IPC Event 14 Event14 = 14, /// IPC Event 15 Event15 = 15, } const EVENTS: [EventNumber; EVENT_COUNT] = [ EventNumber::Event0, EventNumber::Event1, EventNumber::Event2, EventNumber::Event3, EventNumber::Event4, EventNumber::Event5, EventNumber::Event6, EventNumber::Event7, EventNumber::Event8, EventNumber::Event9, EventNumber::Event10, EventNumber::Event11, EventNumber::Event12, EventNumber::Event13, EventNumber::Event14, EventNumber::Event15, ]; /// IPC Channel #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IpcChannel { /// IPC Channel 0 Channel0, /// IPC Channel 1 Channel1, /// IPC Channel 2 Channel2, /// IPC Channel 3 Channel3, /// IPC Channel 4 Channel4, /// IPC Channel 5 Channel5, /// IPC Channel 6 Channel6, /// IPC Channel 7 Channel7, /// IPC Channel 8 Channel8, /// IPC Channel 9 Channel9, /// IPC Channel 10 Channel10, /// IPC Channel 11 Channel11, /// IPC Channel 12 Channel12, /// IPC Channel 13 Channel13, /// IPC Channel 14 Channel14, /// IPC Channel 15 Channel15, } impl IpcChannel { fn mask(self) -> u32 { 1 << (self as u32) } } /// Interrupt Handler pub struct InterruptHandler { _phantom: PhantomData, } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let regs = T::regs(); // Check if an event was generated, and if it was, trigger the corresponding waker for event in EVENTS { if regs.events_receive(event as usize).read() & 0x01 == 0x01 { regs.intenclr().write(|w| w.0 = 0x01 << event as u32); T::state().wakers[event as usize].wake(); } } } } /// IPC driver #[non_exhaustive] pub struct Ipc<'d> { /// Event 0 pub event0: Event<'d>, /// Event 1 pub event1: Event<'d>, /// Event 2 pub event2: Event<'d>, /// Event 3 pub event3: Event<'d>, /// Event 4 pub event4: Event<'d>, /// Event 5 pub event5: Event<'d>, /// Event 6 pub event6: Event<'d>, /// Event 7 pub event7: Event<'d>, /// Event 8 pub event8: Event<'d>, /// Event 9 pub event9: Event<'d>, /// Event 10 pub event10: Event<'d>, /// Event 11 pub event11: Event<'d>, /// Event 12 pub event12: Event<'d>, /// Event 13 pub event13: Event<'d>, /// Event 14 pub event14: Event<'d>, /// Event 15 pub event15: Event<'d>, } impl<'d> Ipc<'d> { /// Create a new IPC driver. pub fn new( _p: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; let r = T::regs(); let state = T::state(); #[rustfmt::skip] let result = Self { // attributes on expressions are experimental event0: Event { number: EventNumber::Event0, r, state, _phantom: PhantomData }, event1: Event { number: EventNumber::Event1, r, state, _phantom: PhantomData }, event2: Event { number: EventNumber::Event2, r, state, _phantom: PhantomData }, event3: Event { number: EventNumber::Event3, r, state, _phantom: PhantomData }, event4: Event { number: EventNumber::Event4, r, state, _phantom: PhantomData }, event5: Event { number: EventNumber::Event5, r, state, _phantom: PhantomData }, event6: Event { number: EventNumber::Event6, r, state, _phantom: PhantomData }, event7: Event { number: EventNumber::Event7, r, state, _phantom: PhantomData }, event8: Event { number: EventNumber::Event8, r, state, _phantom: PhantomData }, event9: Event { number: EventNumber::Event9, r, state, _phantom: PhantomData }, event10: Event { number: EventNumber::Event10, r, state, _phantom: PhantomData }, event11: Event { number: EventNumber::Event11, r, state, _phantom: PhantomData }, event12: Event { number: EventNumber::Event12, r, state, _phantom: PhantomData }, event13: Event { number: EventNumber::Event13, r, state, _phantom: PhantomData }, event14: Event { number: EventNumber::Event14, r, state, _phantom: PhantomData }, event15: Event { number: EventNumber::Event15, r, state, _phantom: PhantomData }, }; result } } /// IPC event pub struct Event<'d> { number: EventNumber, r: pac::ipc::Ipc, state: &'static State, _phantom: PhantomData<&'d ()>, } impl<'d> Event<'d> { /// Trigger the event. pub fn trigger(&self) { let nr = self.number; self.r.tasks_send(nr as usize).write_value(1); } /// Wait for the event to be triggered. pub async fn wait(&mut self) { let nr = self.number as usize; self.r.intenset().write(|w| w.0 = 1 << nr); poll_fn(|cx| { self.state.wakers[nr].register(cx.waker()); if self.r.events_receive(nr).read() == 1 { self.r.events_receive(nr).write_value(0x00); Poll::Ready(()) } else { Poll::Pending } }) .await; } /// Returns the [`EventNumber`] of the event. pub fn number(&self) -> EventNumber { self.number } /// Create a handle that can trigger the event. pub fn trigger_handle(&self) -> EventTrigger<'d> { EventTrigger { number: self.number, r: self.r, _phantom: PhantomData, } } /// Configure the channels the event will broadcast to pub fn configure_trigger>(&mut self, channels: I) { self.r.send_cnf(self.number as usize).write(|w| { for channel in channels { w.0 |= channel.mask(); } }) } /// Configure the channels the event will listen on pub fn configure_wait>(&mut self, channels: I) { self.r.receive_cnf(self.number as usize).write(|w| { for channel in channels { w.0 |= channel.mask(); } }); } /// Get the task for the IPC event to use with PPI. pub fn task(&self) -> ppi::Task<'d> { let nr = self.number as usize; ppi::Task::from_reg(self.r.tasks_send(nr)) } /// Get the event for the IPC event to use with PPI. pub fn event(&self) -> ppi::Event<'d> { let nr = self.number as usize; ppi::Event::from_reg(self.r.events_receive(nr)) } /// Reborrow into a "child" Event. /// /// `self` will stay borrowed until the child Event is dropped. pub fn reborrow(&mut self) -> Event<'_> { Event { number: self.number, r: self.r, state: self.state, _phantom: PhantomData, } } /// Steal an IPC event by number. /// /// # Safety /// /// The event number must not be in use by another [`Event`]. pub unsafe fn steal(number: EventNumber) -> Self { Self { number, r: T::regs(), state: T::state(), _phantom: PhantomData, } } } /// A handle that can trigger an IPC event. /// /// This `struct` is returned by [`Event::trigger_handle`]. pub struct EventTrigger<'d> { number: EventNumber, r: pac::ipc::Ipc, _phantom: PhantomData<&'d ()>, } impl EventTrigger<'_> { /// Trigger the event. pub fn trigger(&self) { let nr = self.number; self.r.tasks_send(nr as usize).write_value(1); } /// Returns the [`EventNumber`] of the event. pub fn number(&self) -> EventNumber { self.number } } pub(crate) struct State { wakers: [AtomicWaker; EVENT_COUNT], } impl State { pub(crate) const fn new() -> Self { Self { wakers: [const { AtomicWaker::new() }; EVENT_COUNT], } } } pub(crate) trait SealedInstance { fn regs() -> pac::ipc::Ipc; fn state() -> &'static State; } /// IPC peripheral instance. #[allow(private_bounds)] pub trait Instance: PeripheralType + SealedInstance + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_ipc { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::ipc::SealedInstance for peripherals::$type { fn regs() -> pac::ipc::Ipc { pac::$pac_type } fn state() -> &'static crate::ipc::State { static STATE: crate::ipc::State = crate::ipc::State::new(); &STATE } } impl crate::ipc::Instance for peripherals::$type { type Interrupt = crate::interrupt::typelevel::$irq; } }; } ================================================ FILE: embassy-nrf/src/lib.rs ================================================ #![no_std] #![allow(async_fn_in_trait)] #![allow(unsafe_op_in_unsafe_fn)] #![cfg_attr( docsrs, doc = "\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 = "nrf54l10-app-s", feature = "nrf54l10-app-ns", feature = "nrf54l05-app-s", feature = "nrf54l05-app-ns", feature = "nrf54lm20-app-s", 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, nrf54l10-app-s, nrf54l10-app-ns, nrf54l05-app-s, nrf54l05-app-ns, nrf54lm20-app-s, 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."); #[cfg(all(feature = "lfxo-pins-as-gpio", not(feature = "_nrf5340")))] compile_error!("feature `lfxo-pins-as-gpio` is only valid for nRF53 series chips."); // 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; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod egu; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; #[cfg(feature = "_nrf5340")] pub mod ipc; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any( feature = "nrf52832", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340-app" ))] pub mod nfct; #[cfg(not(feature = "_nrf54l"))] pub mod nvmc; #[cfg(all(feature = "_nrf54l", feature = "_s"))] pub mod rramc; #[cfg(all(feature = "_nrf54l", feature = "_s"))] pub use rramc as 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; pub mod ppi; #[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(any( feature = "nrf52811", feature = "nrf52820", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340-net" ))] #[cfg(feature = "_net-driver")] pub mod embassy_net_802154_driver; #[cfg(all(feature = "_nrf54l", feature = "_s"))] pub mod cracen; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(feature = "_nrf5340")] pub mod reset; #[cfg(not(feature = "_nrf54l"))] #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod rng; // Currently supported chips #[cfg(any( feature = "nrf52840", all(any(feature = "_nrf91", feature = "_nrf5340-app"), feature = "_s"), ))] pub mod cryptocell_rng; #[cfg(not(feature = "_nrf54l"))] pub mod rtc; #[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-app", 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(not(feature = "_nrf54l"))] // TODO #[cfg(any( feature = "_nrf5340-app", feature = "nrf52820", feature = "nrf52833", feature = "nrf52840" ))] pub mod usb; 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 = "_nrf54l10-app", path = "chips/nrf54l10_app.rs")] #[cfg_attr(feature = "_nrf54l05-app", path = "chips/nrf54l05_app.rs")] #[cfg_attr(feature = "_nrf54lm20-app", path = "chips/nrf54lm20_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 [crate::interrupt::typelevel::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!( /// /// Binds the SPIM3 interrupt. /// 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 { ($(#[$attr:meta])* $vis:vis struct $name:ident { $( $(#[cfg($cond_irq:meta)])? $irq:ident => $( $(#[cfg($cond_handler:meta)])? $handler:ty ),*; )* }) => { #[derive(Copy, Clone)] $(#[$attr])* $vis struct $name; $( #[allow(non_snake_case)] #[unsafe(no_mangle)] $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { unsafe { $( $(#[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::{EASY_DMA_SIZE, Peripherals, peripherals}; pub use embassy_hal_internal::{Peri, PeripheralType}; 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. /// Clock speed #[cfg(feature = "_nrf54l")] pub enum ClockSpeed { /// Run at 128 MHz. CK128, /// Run at 64 MHz. CK64, } /// 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(feature = "_nrf91"))] Synthesized, /// External source from xtal. #[cfg(not(feature = "lfxo-pins-as-gpio"))] ExternalXtal, /// External source from xtal with low swing applied. #[cfg(not(any(feature = "lfxo-pins-as-gpio", feature = "_nrf91", feature = "_nrf54l")))] ExternalLowSwing, /// External source from xtal with full swing applied. #[cfg(not(any(feature = "lfxo-pins-as-gpio", 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(any(feature = "nrf52840", feature = "nrf52833"))] 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(any(feature = "nrf52840", feature = "nrf52833"))] 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, /// Configure the voltage of the high voltage stage. It is stored in non-volatile memory (UICR.VREGHVOUT register); pass None to not touch it. #[cfg(feature = "nrf5340-app-s")] pub regh_voltage: Option, /// 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, } /// Output voltage setting for VREGH regulator stage. #[cfg(feature = "nrf5340-app-s")] pub enum ReghVoltage { /// 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 converter. #[cfg(feature = "_nrf91")] pub struct DcdcConfig { /// Config for the main rail, if disabled LDO will be used. pub regmain: bool, } /// Settings for the internal capacitors. #[cfg(feature = "nrf5340-app-s")] pub struct InternalCapacitors { /// Config for the internal capacitors on pins XC1 and XC2. Pass `None` to not touch it. pub hfxo: Option, /// Config for the internal capacitors between pins XL1 and XL2. Pass `None` to not touch /// it. pub lfxo: Option, } /// Internal capacitance value for the HFXO. #[cfg(feature = "nrf5340-app-s")] #[derive(Copy, Clone)] pub enum HfxoCapacitance { /// Use external capacitors External, /// 7.0 pF _7_0pF, /// 7.5 pF _7_5pF, /// 8.0 pF _8_0pF, /// 8.5 pF _8_5pF, /// 9.0 pF _9_0pF, /// 9.5 pF _9_5pF, /// 10.0 pF _10_0pF, /// 10.5 pF _10_5pF, /// 11.0 pF _11_0pF, /// 11.5 pF _11_5pF, /// 12.0 pF _12_0pF, /// 12.5 pF _12_5pF, /// 13.0 pF _13_0pF, /// 13.5 pF _13_5pF, /// 14.0 pF _14_0pF, /// 14.5 pF _14_5pF, /// 15.0 pF _15_0pF, /// 15.5 pF _15_5pF, /// 16.0 pF _16_0pF, /// 16.5 pF _16_5pF, /// 17.0 pF _17_0pF, /// 17.5 pF _17_5pF, /// 18.0 pF _18_0pF, /// 18.5 pF _18_5pF, /// 19.0 pF _19_0pF, /// 19.5 pF _19_5pF, /// 20.0 pF _20_0pF, } #[cfg(feature = "nrf5340-app-s")] impl HfxoCapacitance { /// The capacitance value times two. pub(crate) fn value2(self) -> i32 { match self { HfxoCapacitance::External => unreachable!(), HfxoCapacitance::_7_0pF => 14, HfxoCapacitance::_7_5pF => 15, HfxoCapacitance::_8_0pF => 16, HfxoCapacitance::_8_5pF => 17, HfxoCapacitance::_9_0pF => 18, HfxoCapacitance::_9_5pF => 19, HfxoCapacitance::_10_0pF => 20, HfxoCapacitance::_10_5pF => 21, HfxoCapacitance::_11_0pF => 22, HfxoCapacitance::_11_5pF => 23, HfxoCapacitance::_12_0pF => 24, HfxoCapacitance::_12_5pF => 25, HfxoCapacitance::_13_0pF => 26, HfxoCapacitance::_13_5pF => 27, HfxoCapacitance::_14_0pF => 28, HfxoCapacitance::_14_5pF => 29, HfxoCapacitance::_15_0pF => 30, HfxoCapacitance::_15_5pF => 31, HfxoCapacitance::_16_0pF => 32, HfxoCapacitance::_16_5pF => 33, HfxoCapacitance::_17_0pF => 34, HfxoCapacitance::_17_5pF => 35, HfxoCapacitance::_18_0pF => 36, HfxoCapacitance::_18_5pF => 37, HfxoCapacitance::_19_0pF => 38, HfxoCapacitance::_19_5pF => 39, HfxoCapacitance::_20_0pF => 40, } } pub(crate) fn external(self) -> bool { matches!(self, Self::External) } } /// Internal capacitance value for the LFXO. #[cfg(feature = "nrf5340-app-s")] pub enum LfxoCapacitance { /// Use external capacitors External = 0, /// 6 pF _6pF = 1, /// 7 pF _7pF = 2, /// 9 pF _9pF = 3, } #[cfg(feature = "nrf5340-app-s")] impl From for super::pac::oscillators::vals::Intcap { fn from(t: LfxoCapacitance) -> Self { match t { LfxoCapacitance::External => Self::EXTERNAL, LfxoCapacitance::_6pF => Self::C6PF, LfxoCapacitance::_7pF => Self::C7PF, LfxoCapacitance::_9pF => Self::C9PF, } } } /// 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(feature = "nrf5340-app-s")] /// Internal capacitor configuration, for use with the `ExternalXtal` clock source. See /// nrf5340-PS §4.12. pub internal_capacitors: InternalCapacitors, #[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, /// Clock speed configuration. #[cfg(feature = "_nrf54l")] pub clock_speed: ClockSpeed, } 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(feature = "nrf5340-app-s")] internal_capacitors: InternalCapacitors { hfxo: None, lfxo: None }, #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] dcdc: DcdcConfig { #[cfg(feature = "nrf52840")] reg0: false, #[cfg(any(feature = "nrf52840", feature = "nrf52833"))] reg0_voltage: None, reg1: false, }, #[cfg(feature = "_nrf5340-app")] dcdc: DcdcConfig { regh: false, #[cfg(feature = "nrf5340-app-s")] regh_voltage: None, 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 = "_nrf54l")] clock_speed: ClockSpeed::CK64, } } } } #[cfg(feature = "_nrf91")] #[allow(unused)] mod consts { pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; pub const UICR_HFXOSRC: *mut u32 = 0x00FF801C as *mut u32; pub const UICR_HFXOCNT: *mut u32 = 0x00FF8020 as *mut u32; pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF802C as *mut u32; pub const APPROTECT_ENABLED: u32 = 0x0000_0000; #[cfg(feature = "_nrf9120")] pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; } #[cfg(feature = "_nrf5340-app")] #[allow(unused)] mod consts { pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; pub const UICR_VREGHVOUT: *mut u32 = 0x00FF8010 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; } /// Result from writing UICR. #[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub 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, } /// Write the UICR value at the provided address, ensuring that flash /// settings are correctly apply to persist the value. /// /// Safety: the address must be a valid UICR register. #[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] pub unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { uicr_write_masked(address, value, 0xFFFF_FFFF) } #[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] /// Write the UICR value at the provided address, ensuring that flash /// settings are correctly apply to persist the value. /// /// Safety: the address must be a valid UICR register. pub 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; } // Nrf9151 errata 7, need to disable interrups + use DSB https://docs.nordicsemi.com/bundle/errata_nRF9151_Rev2/page/ERR/nRF9151/Rev2/latest/anomaly_151_7.html cortex_m::interrupt::free(|_cs| { 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); cortex_m::asm::dsb(); 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; // set clock speed #[cfg(feature = "_nrf54l")] { #[cfg(feature = "_s")] let regs = pac::OSCILLATORS_S; #[cfg(feature = "_ns")] let regs = pac::OSCILLATORS_NS; use pac::oscillators::vals::Freq; regs.pll().freq().write(|w| { w.set_freq(match config.clock_speed { config::ClockSpeed::CK64 => Freq::CK64M, config::ClockSpeed::CK128 => Freq::CK128M, }); }); } // Workaround used in the nrf mdk: file system_nrf91.c , function SystemInit(), after `#if !defined(NRF_SKIP_UICR_HFXO_WORKAROUND)` #[cfg(all(feature = "_nrf91", feature = "_s"))] { let uicr = pac::UICR_S; let hfxocnt = uicr.hfxocnt().read().hfxocnt().to_bits(); let hfxosrc = uicr.hfxosrc().read().hfxosrc().to_bits(); if hfxosrc == 1 { unsafe { let _ = uicr_write(consts::UICR_HFXOSRC, 0); } needs_reset = true; } if hfxocnt == 255 { unsafe { let _ = uicr_write(consts::UICR_HFXOCNT, 32); } needs_reset = true; } } // Apply trimming values from the FICR. #[cfg(any( all(feature = "_nrf5340-app", feature = "_s"), all(feature = "_nrf54l", feature = "_s"), feature = "_nrf5340-net", ))] { #[cfg(feature = "_nrf5340")] let n = 32; #[cfg(feature = "_nrf54l")] let n = 64; for i in 0..n { let info = pac::FICR.trimcnf(i); let addr = info.addr().read(); if addr == 0 || addr == 0xFFFF_FFFF { break; } unsafe { (addr as *mut u32).write_volatile(info.data().read()); } } } // Workaround for anomaly 66 #[cfg(feature = "_nrf52")] { let ficr = pac::FICR; let temp = pac::TEMP; temp.a(0).write_value(ficr.temp().a0().read().0); temp.a(1).write_value(ficr.temp().a1().read().0); temp.a(2).write_value(ficr.temp().a2().read().0); temp.a(3).write_value(ficr.temp().a3().read().0); temp.a(4).write_value(ficr.temp().a4().read().0); temp.a(5).write_value(ficr.temp().a5().read().0); temp.b(0).write_value(ficr.temp().b0().read().0); temp.b(1).write_value(ficr.temp().b1().read().0); temp.b(2).write_value(ficr.temp().b2().read().0); temp.b(3).write_value(ficr.temp().b3().read().0); temp.b(4).write_value(ficr.temp().b4().read().0); temp.b(5).write_value(ficr.temp().b5().read().0); temp.t(0).write_value(ficr.temp().t0().read().0); temp.t(1).write_value(ficr.temp().t1().read().0); temp.t(2).write_value(ficr.temp().t2().read().0); temp.t(3).write_value(ficr.temp().t3().read().0); temp.t(4).write_value(ficr.temp().t4().read().0); } // GLITCHDET is only accessible for secure code #[cfg(all(feature = "_nrf54l", feature = "_s"))] { // The voltage glitch detectors are automatically enabled after reset. // To save power, the glitch detectors must be disabled when not in use. pac::GLITCHDET.config().write(|w| w.set_enable(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://docs.nordicsemi.com/bundle/ps_nrf52840/page/dif.html#d402e184 // UICR.APPROTECT = HwDisabled 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); } } // TAMPC is only accessible for secure code #[cfg(all(feature = "_nrf54l", feature = "_s"))] { use crate::pac::tampc::vals; // UICR cannot be written here, because it can only be written once after an erase all. // The erase all value means that debug access is allowed if permitted by the firmware. // Write to TAMPC to permit debug access // // See https://docs.nordicsemi.com/bundle/ps_nrf54L15/page/debug.html#ariaid-title6 let p = pac::TAMPC; // Unlock dbgen p.protect().domain(0).dbgen().ctrl().write(|w| { w.set_key(vals::DomainDbgenCtrlKey::KEY); w.set_writeprotection(vals::DomainDbgenCtrlWriteprotection::CLEAR); }); p.protect().domain(0).dbgen().ctrl().write(|w| { w.set_key(vals::DomainDbgenCtrlKey::KEY); w.set_value(vals::DomainDbgenCtrlValue::HIGH); }); // Unlock niden p.protect().domain(0).niden().ctrl().write(|w| { w.set_key(vals::NidenCtrlKey::KEY); w.set_writeprotection(vals::NidenCtrlWriteprotection::CLEAR); }); p.protect().domain(0).niden().ctrl().write(|w| { w.set_key(vals::NidenCtrlKey::KEY); w.set_value(vals::NidenCtrlValue::HIGH); }); p.protect().domain(0).spiden().ctrl().write(|w| { w.set_key(vals::SpidenCtrlKey::KEY); w.set_writeprotection(vals::SpidenCtrlWriteprotection::CLEAR); }); p.protect().domain(0).spiden().ctrl().write(|w| { w.set_key(vals::SpidenCtrlKey::KEY); w.set_value(vals::SpidenCtrlValue::HIGH); }); p.protect().domain(0).spniden().ctrl().write(|w| { w.set_key(vals::SpnidenCtrlKey::KEY); w.set_writeprotection(vals::SpnidenCtrlWriteprotection::CLEAR); }); p.protect().domain(0).spniden().ctrl().write(|w| { w.set_key(vals::SpnidenCtrlKey::KEY); w.set_value(vals::SpnidenCtrlValue::HIGH); }); } // nothing to do on the nrf9160, debug is allowed by default. // nrf9151, nrf9161 use the new-style approtect that requires writing a register. #[cfg(feature = "nrf9120-s")] unsafe { let p = pac::APPROTECT_S; let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); needs_reset |= res == WriteResult::Written; p.approtect() .disable() .write(|w| w.set_disable(pac::approtect::vals::ApprotectDisableDisable::SW_UNPROTECTED)); let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); needs_reset |= res == WriteResult::Written; p.secureapprotect() .disable() .write(|w| w.set_disable(pac::approtect::vals::SecureapprotectDisableDisable::SW_UNPROTECTED)); // TODO: maybe add workaround for this errata // It uses extra power, not sure how to let the user choose. // https://docs.nordicsemi.com/bundle/errata_nRF9151_Rev1/page/ERR/nRF9151/Rev1/latest/anomaly_151_36.html#anomaly_151_36 } } config::Debug::Disallowed => { // TODO: Handle nRF54L // By default, debug access is not allowed if the firmware doesn't allow it. // Code could be added here to disable debug access in UICR as well. #[cfg(not(feature = "_nrf54l"))] 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; } #[cfg(feature = "nrf9120-s")] { let p = pac::APPROTECT_S; p.approtect().forceprotect().write(|w| w.set_forceprotect(true)); p.secureapprotect().forceprotect().write(|w| w.set_forceprotect(true)); } } } 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", all(feature = "_nrf5340-app", feature = "_s")))] 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(any(feature = "nrf52840", feature = "nrf52833"))] 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`." ); } } } #[cfg(feature = "nrf5340-app-s")] unsafe { if let Some(value) = config.dcdc.regh_voltage { let value = value as u32; let res = uicr_write_masked(consts::UICR_VREGHVOUT, 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(); } // Configure internal capacitors #[cfg(feature = "nrf5340-app-s")] { if let Some(cap) = config.internal_capacitors.hfxo { if cap.external() { pac::OSCILLATORS.xosc32mcaps().write(|w| w.set_enable(false)); } else { let mut slope = pac::FICR.xosc32mtrim().read().slope() as i32; let offset = pac::FICR.xosc32mtrim().read().offset() as i32; // slope is a signed 5-bit integer if slope >= 16 { slope -= 32; } let capvalue = (((slope + 56) * (cap.value2() - 14)) + ((offset - 8) << 4) + 32) >> 6; pac::OSCILLATORS.xosc32mcaps().write(|w| { w.set_capvalue(capvalue as u8); w.set_enable(true); }); } } if let Some(cap) = config.internal_capacitors.lfxo { pac::OSCILLATORS.xosc32ki().intcap().write(|w| w.set_intcap(cap.into())); } } 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 {} } } } // Workaround for anomaly 140 #[cfg(feature = "nrf5340-app-s")] if unsafe { (0x50032420 as *mut u32).read_volatile() } & 0x80000000 != 0 { r.events_lfclkstarted().write_value(0); r.lfclksrc() .write(|w| w.set_src(nrf_pac::clock::vals::Lfclksrc::LFSYNT)); r.tasks_lfclkstart().write_value(1); while r.events_lfclkstarted().read() == 0 {} r.events_lfclkstarted().write_value(0); r.tasks_lfclkstop().write_value(1); r.lfclksrc().write(|w| w.set_src(nrf_pac::clock::vals::Lfclksrc::LFRC)); } // 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 = "_nrf5340")] { #[allow(unused_mut)] let mut lfxo = false; match config.lfclk_source { config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), config::LfclkSource::Synthesized => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFSYNT)), #[cfg(not(feature = "lfxo-pins-as-gpio"))] config::LfclkSource::ExternalXtal => lfxo = true, #[cfg(not(feature = "lfxo-pins-as-gpio"))] config::LfclkSource::ExternalLowSwing => lfxo = true, #[cfg(not(feature = "lfxo-pins-as-gpio"))] config::LfclkSource::ExternalFullSwing => { #[cfg(feature = "_nrf5340-app")] pac::OSCILLATORS.xosc32ki().bypass().write(|w| w.set_bypass(true)); lfxo = true; } } if lfxo { if cfg!(feature = "_s") { // MCUSEL is only accessible from secure code. let p0 = pac::P0; p0.pin_cnf(0) .write(|w| w.set_mcusel(pac::gpio::vals::Mcusel::PERIPHERAL)); p0.pin_cnf(1) .write(|w| w.set_mcusel(pac::gpio::vals::Mcusel::PERIPHERAL)); } r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)); } } #[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)); } } #[cfg(feature = "_nrf54l")] { // Turn on DCDC // From Product specification: // "Once the device starts, the DC/DC regulator must be enabled using register VREGMAIN.DCDCEN. // When enabling the DC/DC regulator, the device checks if an inductor is connected to the DCC pin. // If an inductor is not detected, the device remains in LDO mode" pac::REGULATORS.vregmain().dcdcen().write(|w| w.set_val(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 } /// 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); } ================================================ FILE: embassy-nrf/src/nfct.rs ================================================ //! 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::{Ordering, compiler_fence}; use core::task::Poll; use embassy_sync::waitqueue::AtomicWaker; pub use vals::{Bitframesdd as SddPat, Discardmode as DiscardMode}; use crate::interrupt::InterruptExt; use crate::pac::NFCT; use crate::pac::nfct::vals; use crate::peripherals::NFCT; use crate::util::slice_in_ram; use crate::{Peri, interrupt, pac}; /// 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: Peri<'d, NFCT>, rx_buf: [u8; 256], tx_buf: [u8; 256], } impl<'d> NfcT<'d> { /// Create an Nfc Tag driver pub fn new( _p: Peri<'d, NFCT>, _irq: impl interrupt::typelevel::Binding + 'd, config: &Config, ) -> Self { 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); } ================================================ FILE: embassy-nrf/src/nvmc.rs ================================================ //! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver. use core::{ptr, slice}; use embedded_storage::nor_flash::{ ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, }; use crate::pac::nvmc::vals; use crate::peripherals::NVMC; use crate::{Peri, pac}; #[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: Peri<'d, NVMC>, } impl<'d> Nvmc<'d> { /// Create Nvmc driver. pub fn new(_p: Peri<'d, NVMC>) -> Self { 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(()) } } ================================================ FILE: embassy-nrf/src/pdm.rs ================================================ //! Pulse Density Modulation (PDM) microphone driver #![macro_use] use core::future::poll_fn; use core::marker::PhantomData; use core::sync::atomic::{Ordering, compiler_fence}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::I7F1; use crate::chip::EASY_DMA_SIZE; use crate::gpio::{AnyPin, DISCONNECTED, Pin as GpioPin, SealedPin}; 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}; /// 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> { r: pac::pdm::Pdm, state: &'static State, _phantom: PhantomData<&'d ()>, } /// 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> Pdm<'d> { /// Create PDM driver pub fn new( pdm: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, clk: Peri<'d, impl GpioPin>, din: Peri<'d, impl GpioPin>, config: Config, ) -> Self { Self::new_inner(pdm, clk.into(), din.into(), config) } fn new_inner(_pdm: Peri<'d, T>, clk: Peri<'d, AnyPin>, din: Peri<'d, AnyPin>, config: Config) -> Self { 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 { r: T::regs(), state: T::state(), _phantom: PhantomData, } } 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(self.r, 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) { // start dummy sampling because microphone needs some setup time self.r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); self.r .sample() .maxcnt() .write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); self.r.tasks_start().write_value(1); } /// Stop sampling microphone data inta a dummy buffer pub async fn stop(&mut self) { self.r.tasks_stop().write_value(1); self.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); } if self.r.events_started().read() == 0 { return Err(Error::NotRunning); } let r = self.r; 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(); self.r.sample().ptr().write_value(ptr as u32); self.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 self.r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); self.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(&mut self) { self.r.events_end().write_value(0); self.r.intenset().write(|w| w.set_end(true)); compiler_fence(Ordering::SeqCst); let state = self.state; let r = self.r; poll_fn(|cx| { 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, { if self.r.events_started().read() != 0 { return Err(Error::AlreadyRunning); } self.r.sample().ptr().write_value(bufs[0].as_mut_ptr() as u32); self.r.sample().maxcnt().write(|w| w.set_buffsize(N as _)); // Reset and enable the events self.r.events_end().write_value(0); self.r.events_started().write_value(0); self.r.events_stopped().write_value(0); self.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); self.r.tasks_start().write_value(1); let mut current_buffer = 0; let mut done = false; let r = self.r; let drop = OnDrop::new(move || { 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 {} }); let state = self.state; let r = self.r; // Wait for events and complete when the sampler indicates it has had enough poll_fn(|cx| { 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> Drop for Pdm<'d> { fn drop(&mut self) { self.r.tasks_stop().write_value(1); self.r.enable().write(|w| w.set_enable(false)); self.r.psel().din().write_value(DISCONNECTED); self.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: SealedInstance + PeripheralType + '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; } }; } ================================================ FILE: embassy-nrf/src/power.rs ================================================ //! 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)); } ================================================ FILE: embassy-nrf/src/ppi/dppi.rs ================================================ use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; use crate::Peri; const DPPI_ENABLE_BIT: u32 = 0x8000_0000; const DPPI_CHANNEL_MASK: u32 = 0x0000_00FF; #[cfg(not(feature = "_nrf54l"))] pub(crate) fn regs() -> crate::pac::dppic::Dppic { crate::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: Peri<'d, C>, 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: Peri<'d, C>, 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: Peri<'d, C>, events: [Event<'d>; EVENT_COUNT], tasks: [Task<'d>; TASK_COUNT]) -> Self { 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(); self.ch.regs().chenset().write(|w| w.0 = 1 << n); } /// Disables the channel. pub fn disable(&mut self) { let n = self.ch.number(); self.ch.regs().chenclr().write(|w| w.0 = 1 << n); } } impl Ppi<'static, C, EVENT_COUNT, TASK_COUNT> { /// Persist the channel's configuration for the rest of the program's lifetime. This method /// should be preferred over [`core::mem::forget()`] because the `'static` bound prevents /// accidental reuse of the underlying peripheral. pub fn persist(self) { core::mem::forget(self); } } 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) } } } } ================================================ FILE: embassy-nrf/src/ppi/mod.rs ================================================ #![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::{Peri, PeripheralType, impl_peripheral}; use crate::pac::common::{RW, Reg, W}; use crate::pac::{self}; #[cfg_attr(feature = "_dppi", path = "dppi.rs")] #[cfg_attr(feature = "_ppi", path = "ppi.rs")] mod _version; #[allow(unused_imports)] pub(crate) use _version::*; /// PPI channel driver. pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> { ch: Peri<'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: Peri<'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: Peri<'d, G>) -> Self { let r = g.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 = self.g.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 = self.g.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(); self.g.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(); self.g.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(self.g.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(self.g.regs().tasks_chg(n).dis()) } } impl PpiGroup<'static, G> { /// Persist this group's configuration for the rest of the program's lifetime. This method /// should be preferred over [`core::mem::forget()`] because the `'static` bound prevents /// accidental reuse of the underlying peripheral. pub fn persist(self) { core::mem::forget(self); } } impl<'d, G: Group> Drop for PpiGroup<'d, G> { fn drop(&mut self) { let r = self.g.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 { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic; } pub(crate) trait SealedGroup { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic; #[cfg(not(feature = "_dppi"))] fn regs(&self) -> pac::ppi::Ppi; } /// Interface for PPI channels. #[allow(private_bounds)] pub trait Channel: SealedChannel + PeripheralType + 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 {} /// Interface for PPI channels that cannot be configured. pub trait StaticChannel: Channel + Into {} /// Interface for a group of PPI channels. #[allow(private_bounds)] pub trait Group: SealedGroup + PeripheralType + Into + Sized + 'static { /// Returns the number of the group. fn number(&self) -> usize; } // ====================== // 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, #[cfg(feature = "_dppi")] pub(crate) regs: pac::dppic::Dppic, } impl_peripheral!(AnyStaticChannel); impl SealedChannel for AnyStaticChannel { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic { self.regs } } impl Channel for AnyStaticChannel { fn number(&self) -> usize { self.number as usize } } impl StaticChannel for AnyStaticChannel {} /// 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, #[cfg(feature = "_dppi")] pub(crate) regs: pac::dppic::Dppic, } impl_peripheral!(AnyConfigurableChannel); impl SealedChannel for AnyConfigurableChannel { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic { self.regs } } impl Channel for AnyConfigurableChannel { fn number(&self) -> usize { self.number as usize } } impl ConfigurableChannel for AnyConfigurableChannel {} #[cfg(not(feature = "_nrf51"))] macro_rules! impl_ppi_channel { ($type:ident, $inst:ident, $number:expr) => { impl crate::ppi::SealedChannel for peripherals::$type { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic { pac::$inst } } impl crate::ppi::Channel for peripherals::$type { fn number(&self) -> usize { $number } } }; ($type:ident, $inst:ident, $number:expr => static) => { impl_ppi_channel!($type, $inst, $number); impl crate::ppi::StaticChannel for peripherals::$type {} impl From for crate::ppi::AnyStaticChannel { fn from(val: peripherals::$type) -> Self { Self { number: crate::ppi::Channel::number(&val) as u8, #[cfg(feature = "_dppi")] regs: pac::$inst, } } } }; ($type:ident, $inst:ident, $number:expr => configurable) => { impl_ppi_channel!($type, $inst, $number); impl crate::ppi::ConfigurableChannel for peripherals::$type {} impl From for crate::ppi::AnyConfigurableChannel { fn from(val: peripherals::$type) -> Self { Self { number: crate::ppi::Channel::number(&val) as u8, #[cfg(feature = "_dppi")] regs: pac::$inst, } } } }; } // ====================== // groups /// A type erased PPI group. pub struct AnyGroup { pub(crate) number: u8, #[cfg(feature = "_dppi")] pub(crate) regs: pac::dppic::Dppic, #[cfg(not(feature = "_dppi"))] pub(crate) regs: pac::ppi::Ppi, } impl_peripheral!(AnyGroup); impl SealedGroup for AnyGroup { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic { self.regs } #[cfg(not(feature = "_dppi"))] fn regs(&self) -> pac::ppi::Ppi { self.regs } } impl Group for AnyGroup { fn number(&self) -> usize { self.number as usize } } macro_rules! impl_ppi_group { ($type:ident, $inst:ident, $number:expr) => { impl crate::ppi::SealedGroup for crate::peripherals::$type { #[cfg(feature = "_dppi")] fn regs(&self) -> pac::dppic::Dppic { pac::$inst } #[cfg(not(feature = "_dppi"))] fn regs(&self) -> pac::ppi::Ppi { pac::$inst } } impl crate::ppi::Group for crate::peripherals::$type { fn number(&self) -> usize { $number } } impl From for crate::ppi::AnyGroup { fn from(val: crate::peripherals::$type) -> Self { Self { number: crate::ppi::Group::number(&val) as u8, regs: pac::$inst, } } } }; } ================================================ FILE: embassy-nrf/src/ppi/ppi.rs ================================================ use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; use crate::{Peri, pac}; 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: Peri<'d, C>, task: Task) -> Self { 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: Peri<'d, C>, event: Event<'d>, task: Task<'d>) -> Self { 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: Peri<'d, C>, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { 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 Ppi<'static, C, EVENT_COUNT, TASK_COUNT> { /// Persist the channel's configuration for the rest of the program's lifetime. This method /// should be preferred over [`core::mem::forget()`] because the `'static` bound prevents /// accidental reuse of the underlying peripheral. pub fn persist(self) { core::mem::forget(self); } } 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); } } ================================================ FILE: embassy-nrf/src/pwm.rs ================================================ //! Pulse Width Modulation (PWM) driver. #![macro_use] use core::sync::atomic::{Ordering, compiler_fence}; use embassy_hal_internal::{Peri, PeripheralType}; use crate::gpio::{AnyPin, DISCONNECTED, Level, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _, convert_drive}; 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}; /// 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> { r: pac::pwm::Pwm, duty: [DutyCycle; 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> { r: pac::pwm::Pwm, 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> SequencePwm<'d> { /// Create a new 1-channel PWM pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, config: Config) -> Result { Self::new_inner(pwm, Some(ch0.into()), None, None, None, config) } /// Create a new 2-channel PWM pub fn new_2ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, config: Config, ) -> Result { Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), None, None, config) } /// Create a new 3-channel PWM pub fn new_3ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, ch2: Peri<'d, impl GpioPin>, config: Config, ) -> Result { Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), None, config) } /// Create a new 4-channel PWM pub fn new_4ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, ch2: Peri<'d, impl GpioPin>, ch3: Peri<'d, impl GpioPin>, config: Config, ) -> Result { Self::new_inner( pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), Some(ch3.into()), config, ) } fn new_inner( _pwm: Peri<'d, T>, ch0: Option>, ch1: Option>, ch2: Option>, ch3: Option>, config: Config, ) -> Result { let r = T::regs(); let channels = [ (&ch0, config.ch0_drive, config.ch0_idle_level), (&ch1, config.ch1_drive, config.ch1_idle_level), (&ch2, config.ch2_drive, config.ch2_idle_level), (&ch3, config.ch3_drive, config.ch3_idle_level), ]; for (i, (pin, drive, idle_level)) in channels.into_iter().enumerate() { if let Some(pin) = pin { match idle_level { Level::Low => pin.set_low(), Level::High => pin.set_high(), } pin.conf().write(|w| { w.set_dir(gpiovals::Dir::OUTPUT); w.set_input(gpiovals::Input::DISCONNECT); convert_drive(w, drive); }); } r.psel().out(i).write_value(pin.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 { r, ch0, ch1, ch2, ch3 }) } /// Returns reference to `Stopped` event endpoint for PPI. #[inline(always)] pub fn event_stopped(&self) -> Event<'d> { Event::from_reg(self.r.events_stopped()) } /// Returns reference to `LoopsDone` event endpoint for PPI. #[inline(always)] pub fn event_loops_done(&self) -> Event<'d> { Event::from_reg(self.r.events_loopsdone()) } /// Returns reference to `PwmPeriodEnd` event endpoint for PPI. #[inline(always)] pub fn event_pwm_period_end(&self) -> Event<'d> { Event::from_reg(self.r.events_pwmperiodend()) } /// Returns reference to `Seq0 End` event endpoint for PPI. #[inline(always)] pub fn event_seq_end(&self) -> Event<'d> { Event::from_reg(self.r.events_seqend(0)) } /// Returns reference to `Seq1 End` event endpoint for PPI. #[inline(always)] pub fn event_seq1_end(&self) -> Event<'d> { Event::from_reg(self.r.events_seqend(1)) } /// Returns reference to `Seq0 Started` event endpoint for PPI. #[inline(always)] pub fn event_seq0_started(&self) -> Event<'d> { Event::from_reg(self.r.events_seqstarted(0)) } /// Returns reference to `Seq1 Started` event endpoint for PPI. #[inline(always)] pub fn event_seq1_started(&self) -> Event<'d> { Event::from_reg(self.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> { Task::from_reg(self.r.tasks_dma().seq(0).start()) } /// 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> { Task::from_reg(self.r.tasks_dma().seq(1).start()) } /// 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> { Task::from_reg(self.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> { Task::from_reg(self.r.tasks_stop()) } } impl<'a> Drop for SequencePwm<'a> { fn drop(&mut self) { if let Some(pin) = &self.ch0 { pin.set_low(); pin.conf().write(|_| ()); self.r.psel().out(0).write_value(DISCONNECTED); } if let Some(pin) = &self.ch1 { pin.set_low(); pin.conf().write(|_| ()); self.r.psel().out(1).write_value(DISCONNECTED); } if let Some(pin) = &self.ch2 { pin.set_low(); pin.conf().write(|_| ()); self.r.psel().out(2).write_value(DISCONNECTED); } if let Some(pin) = &self.ch3 { pin.set_low(); pin.conf().write(|_| ()); self.r.psel().out(3).write_value(DISCONNECTED); } self.r.enable().write(|w| w.set_enable(false)); } } /// Configuration for the PWM as a whole. #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[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, /// Output level for the channel 0 line when PWM if disabled. pub ch0_idle_level: Level, /// Output level for the channel 1 line when PWM if disabled. pub ch1_idle_level: Level, /// Output level for the channel 2 line when PWM if disabled. pub ch2_idle_level: Level, /// Output level for the channel 3 line when PWM if disabled. pub ch3_idle_level: Level, } impl Default for Config { fn default() -> Self { Self { 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, ch0_idle_level: Level::Low, ch1_idle_level: Level::Low, ch2_idle_level: Level::Low, ch3_idle_level: Level::Low, } } } /// Configuration for the simple PWM driver. #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct SimpleConfig { /// 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, /// 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, /// Output level for the channel 0 line when PWM if disabled. pub ch0_idle_level: Level, /// Output level for the channel 1 line when PWM if disabled. pub ch1_idle_level: Level, /// Output level for the channel 2 line when PWM if disabled. pub ch2_idle_level: Level, /// Output level for the channel 3 line when PWM if disabled. pub ch3_idle_level: Level, } impl Default for SimpleConfig { fn default() -> Self { Self { counter_mode: CounterMode::Up, max_duty: 1000, prescaler: Prescaler::Div16, ch0_drive: OutputDrive::Standard, ch1_drive: OutputDrive::Standard, ch2_drive: OutputDrive::Standard, ch3_drive: OutputDrive::Standard, ch0_idle_level: Level::Low, ch1_idle_level: Level::Low, ch2_idle_level: Level::Low, ch3_idle_level: Level::Low, } } } /// Configuration per sequence #[non_exhaustive] #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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] #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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> { sequencer: Sequencer<'d, 's>, } impl<'d, 's> SingleSequencer<'d, 's> { /// Create a new sequencer pub fn new(pwm: &'s mut SequencePwm<'d>, 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> { _pwm: &'s mut SequencePwm<'d>, sequence0: Sequence<'s>, sequence1: Option>, } #[cfg(feature = "_nrf54l")] fn pwmseq(r: pac::pwm::Pwm, n: usize) -> pac::pwm::PwmSeq { r.seq(n) } #[cfg(not(feature = "_nrf54l"))] fn pwmseq(r: pac::pwm::Pwm, n: usize) -> pac::pwm::DmaSeq { r.dma().seq(n) } #[cfg(feature = "_nrf54l")] const CNT_UNIT: u32 = 2; #[cfg(not(feature = "_nrf54l"))] const CNT_UNIT: u32 = 1; impl<'d, 's> Sequencer<'d, 's> { /// 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>, 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 = self._pwm.r; pwmseq(r, 0).refresh().write(|w| w.0 = sequence0.config.refresh); pwmseq(r, 0).enddelay().write(|w| w.0 = sequence0.config.end_delay); r.dma().seq(0).ptr().write_value(sequence0.words.as_ptr() as u32); r.dma() .seq(0) .maxcnt() .write(|w| w.0 = sequence0.words.len() as u32 * CNT_UNIT); pwmseq(r, 1).refresh().write(|w| w.0 = alt_sequence.config.refresh); pwmseq(r, 1).enddelay().write(|w| w.0 = alt_sequence.config.end_delay); r.dma().seq(1).ptr().write_value(alt_sequence.words.as_ptr() as u32); r.dma() .seq(1) .maxcnt() .write(|w| w.0 = alt_sequence.words.len() as u32 * CNT_UNIT); 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 { SequenceMode::Loop(n) => { r.loop_().write(|w| w.set_cnt(vals::LoopCnt::from_bits(n))); } // 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_dma_seq0_start(true)); } } r.tasks_dma().seq(seqstart_index).start().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 = self._pwm.r; r.shorts().write(|_| ()); compiler_fence(Ordering::SeqCst); r.tasks_stop().write_value(1); r.enable().write(|w| w.set_enable(false)); } } impl<'d, 's> Drop for Sequencer<'d, 's> { fn drop(&mut self) { self.stop(); } } /// How many times to run a single sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CounterMode { /// Up counter (edge-aligned PWM duty cycle) Up, /// Up and down counter (center-aligned PWM duty cycle) UpAndDown, } /// Duty value and polarity for a single channel. /// /// If the channel has inverted polarity, the output is set high as long as the counter is below the duty value. #[repr(transparent)] #[derive(Eq, PartialEq, Clone, Copy)] pub struct DutyCycle { /// The raw duty cycle valuea. /// /// This has the duty cycle in the lower 15 bits. /// The highest bit indicates that the duty cycle has inverted polarity. raw: u16, } impl DutyCycle { /// Make a new duty value with normal polarity. /// /// The value is truncated to 15 bits. /// /// The output is set high if the counter is at or above the duty value. pub const fn normal(value: u16) -> Self { let raw = value & 0x7FFF; Self { raw } } /// Make a new duty cycle with inverted polarity. /// /// The value is truncated to 15 bits. /// /// The output is set high if the counter is below the duty value. pub const fn inverted(value: u16) -> Self { let raw = value | 0x8000; Self { raw } } /// Adjust the polarity of the duty cycle (returns a new object). #[must_use = "this function return a new object, it does not modify self"] pub const fn with_inverted(self, inverted_polarity: bool) -> Self { if inverted_polarity { Self::inverted(self.value()) } else { Self::normal(self.value()) } } /// Gets the 15-bit value of the duty cycle. pub const fn value(&self) -> u16 { self.raw & 0x7FFF } /// Checks if the duty period has inverted polarity. /// /// If the channel has inverted polarity, the output is set high as long as the counter is below the duty value. pub const fn is_inverted(&self) -> bool { self.raw & 0x8000 != 0 } } impl core::fmt::Debug for DutyCycle { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("DutyCycle") .field("value", &self.value()) .field("inverted", &self.is_inverted()) .finish() } } #[cfg(feature = "defmt")] impl defmt::Format for DutyCycle { fn format(&self, f: defmt::Formatter) { defmt::write!( f, "DutyCycle {{ value: {=u16}, inverted: {=bool} }}", self.value(), self.is_inverted(), ); } } impl<'d> SimplePwm<'d> { /// Create a new 1-channel PWM pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, config: &SimpleConfig) -> Self { Self::new_inner(pwm, Some(ch0.into()), None, None, None, config) } /// Create a new 2-channel PWM pub fn new_2ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, config: &SimpleConfig, ) -> Self { Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), None, None, config) } /// Create a new 3-channel PWM pub fn new_3ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, ch2: Peri<'d, impl GpioPin>, config: &SimpleConfig, ) -> Self { Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), None, config) } /// Create a new 4-channel PWM pub fn new_4ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, ch2: Peri<'d, impl GpioPin>, ch3: Peri<'d, impl GpioPin>, config: &SimpleConfig, ) -> Self { Self::new_inner( pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), Some(ch3.into()), config, ) } fn new_inner( _pwm: Peri<'d, T>, ch0: Option>, ch1: Option>, ch2: Option>, ch3: Option>, config: &SimpleConfig, ) -> Self { let r = T::regs(); let channels = [ (&ch0, config.ch0_drive, config.ch0_idle_level), (&ch1, config.ch1_drive, config.ch1_idle_level), (&ch2, config.ch2_drive, config.ch2_idle_level), (&ch3, config.ch3_drive, config.ch3_idle_level), ]; for (i, (pin, drive, idle_level)) in channels.into_iter().enumerate() { if let Some(pin) = pin { match idle_level { Level::Low => pin.set_low(), Level::High => pin.set_high(), } pin.conf().write(|w| { w.set_dir(gpiovals::Dir::OUTPUT); w.set_input(gpiovals::Input::DISCONNECT); convert_drive(w, drive); }); } r.psel().out(i).write_value(pin.psel_bits()); } let pwm = Self { r, ch0, ch1, ch2, ch3, duty: [const { DutyCycle::normal(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.dma().seq(0).ptr().write_value((pwm.duty).as_ptr() as u32); r.dma().seq(0).maxcnt().write(|w| w.0 = 4 * CNT_UNIT); pwmseq(r, 0).refresh().write(|w| w.0 = 0); pwmseq(r, 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| 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)); 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 { self.r.enable().read().enable() } /// Enables the PWM generator. #[inline(always)] pub fn enable(&self) { self.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) { self.r.enable().write(|w| w.set_enable(false)); } /// Returns the current duty of the channel. pub fn duty(&self, channel: usize) -> DutyCycle { self.duty[channel] } /// Sets duty cycle (15 bit) and polarity for a PWM channel. pub fn set_duty(&mut self, channel: usize, duty: DutyCycle) { self.duty[channel] = duty; self.sync_duty_cyles_to_peripheral(); } /// Sets the duty cycle (15 bit) and polarity for all PWM channels. /// /// You can safely set the duty cycle of disabled PWM channels. /// /// When using this function, a single DMA transfer sets all the duty cycles. /// If you call [`Self::set_duty()`] multiple times, /// each duty cycle will be set by a separate DMA transfer. pub fn set_all_duties(&mut self, duty: [DutyCycle; 4]) { self.duty = duty; self.sync_duty_cyles_to_peripheral(); } /// Transfer the duty cycles from `self` to the peripheral. fn sync_duty_cyles_to_peripheral(&self) { // reload ptr in case self was moved self.r.dma().seq(0).ptr().write_value((self.duty).as_ptr() as u32); // defensive before seqstart compiler_fence(Ordering::SeqCst); self.r.events_seqend(0).write_value(0); // tasks_seqstart() doesn't exist in all svds so write its bit instead self.r.tasks_dma().seq(0).start().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 self.r.events_seqend(0).read() == 0 {} } } /// Sets the PWM clock prescaler. #[inline(always)] pub fn set_prescaler(&self, div: Prescaler) { self.r .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 self.r.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) { self.r.countertop().write(|w| w.set_countertop(duty.min(32767u16))); } /// Returns the maximum duty cycle value. #[inline(always)] pub fn max_duty(&self) -> u16 { self.r.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> Drop for SimplePwm<'a> { fn drop(&mut self) { let r = &self.r; 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: SealedInstance + PeripheralType + '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; } }; } ================================================ FILE: embassy-nrf/src/qdec.rs ================================================ //! Quadrature decoder (QDEC) driver. #![macro_use] use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::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}; /// Quadrature decoder driver. pub struct Qdec<'d> { r: pac::qdec::Qdec, state: &'static State, _phantom: PhantomData<&'d ()>, } /// 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> Qdec<'d> { /// Create a new QDEC. pub fn new( qdec: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, a: Peri<'d, impl GpioPin>, b: Peri<'d, impl GpioPin>, config: Config, ) -> Self { Self::new_inner(qdec, a.into(), b.into(), None, config) } /// Create a new QDEC, with a pin for LED output. pub fn new_with_led( qdec: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, a: Peri<'d, impl GpioPin>, b: Peri<'d, impl GpioPin>, led: Peri<'d, impl GpioPin>, config: Config, ) -> Self { Self::new_inner(qdec, a.into(), b.into(), Some(led.into()), config) } fn new_inner( _p: Peri<'d, T>, a: Peri<'d, AnyPin>, b: Peri<'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 { r: T::regs(), state: T::state(), _phantom: PhantomData, } } /// 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 { self.r.intenset().write(|w| w.set_reportrdy(true)); self.r.tasks_readclracc().write_value(1); let state = self.state; let r = self.r; poll_fn(move |cx| { state.waker.register(cx.waker()); if r.events_reportrdy().read() == 0 { Poll::Pending } else { r.events_reportrdy().write_value(0); let acc = r.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: SealedInstance + PeripheralType + '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; } }; } ================================================ FILE: embassy-nrf/src/qspi.rs ================================================ //! Quad Serial Peripheral Interface (QSPI) flash driver. #![macro_use] use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::ptr; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, PeripheralType}; 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}; /// 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> { r: pac::qspi::Qspi, state: &'static State, dpm_enabled: bool, capacity: u32, _phantom: PhantomData<&'d ()>, } impl<'d> Qspi<'d> { /// Create a new QSPI driver. pub fn new( _qspi: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, sck: Peri<'d, impl GpioPin>, csn: Peri<'d, impl GpioPin>, io0: Peri<'d, impl GpioPin>, io1: Peri<'d, impl GpioPin>, io2: Peri<'d, impl GpioPin>, io3: Peri<'d, impl GpioPin>, config: Config, ) -> Self { 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 { r: T::regs(), state: T::state(), dpm_enabled: config.deep_power_down.is_some(), capacity: config.capacity, _phantom: PhantomData, }; 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); } } self.r.cinstrdat0().write(|w| w.0 = dat0); self.r.cinstrdat1().write(|w| w.0 = dat1); self.r.events_ready().write_value(0); self.r.intenset().write(|w| w.set_ready(true)); self.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 dat0 = self.r.cinstrdat0().read().0; let dat1 = self.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(()) } fn wait_ready(&mut self) -> impl Future { let r = self.r; let s = self.state; poll_fn(move |cx| { s.waker.register(cx.waker()); if r.events_ready().read() != 0 { return Poll::Ready(()); } Poll::Pending }) } fn blocking_wait_ready() { loop { let r = pac::QSPI; 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); self.r.read().src().write_value(address); self.r.read().dst().write_value(data.as_ptr() as u32); self.r.read().cnt().write(|w| w.set_cnt(data.len() as u32)); self.r.events_ready().write_value(0); self.r.intenset().write(|w| w.set_ready(true)); self.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); self.r.write().src().write_value(data.as_ptr() as u32); self.r.write().dst().write_value(address); self.r.write().cnt().write(|w| w.set_cnt(data.len() as u32)); self.r.events_ready().write_value(0); self.r.intenset().write(|w| w.set_ready(true)); self.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); self.r.erase().ptr().write_value(address); self.r.erase().len().write(|w| w.set_len(vals::Len::_4KB)); self.r.events_ready().write_value(0); self.r.intenset().write(|w| w.set_ready(true)); self.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> Drop for Qspi<'d> { fn drop(&mut self) { if self.dpm_enabled { trace!("qspi: doing deep powerdown..."); self.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 !self.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. self.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) } self.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(self.r.psel().sck().read()); gpio::deconfigure_pin(self.r.psel().io0().read()); gpio::deconfigure_pin(self.r.psel().io1().read()); gpio::deconfigure_pin(self.r.psel().io2().read()); gpio::deconfigure_pin(self.r.psel().io3().read()); trace!("qspi: dropped"); } } impl<'d> ErrorType for Qspi<'d> { type Error = Error; } impl NorFlashError for Error { fn kind(&self) -> NorFlashErrorKind { NorFlashErrorKind::Other } } impl<'d> ReadNorFlash for Qspi<'d> { 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> NorFlash for Qspi<'d> { 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> embedded_storage::nor_flash::MultiwriteNorFlash for Qspi<'d> {} mod _eh1 { use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; use super::*; impl<'d> AsyncNorFlash for Qspi<'d> { 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> AsyncReadNorFlash for Qspi<'d> { 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> embedded_storage_async::nor_flash::MultiwriteNorFlash for Qspi<'d> {} } /// 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: SealedInstance + PeripheralType + '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; } }; } ================================================ FILE: embassy-nrf/src/radio/ieee802154.rs ================================================ //! IEEE 802.15.4 radio driver use core::marker::PhantomData; use core::sync::atomic::{Ordering, compiler_fence}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use super::{Error, InterruptHandler, TxPower}; use crate::Peri; use crate::interrupt::typelevel::Interrupt; use crate::interrupt::{self}; use crate::pac::radio::vals; pub use crate::pac::radio::vals::State as RadioState; use crate::radio::Instance; /// 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> { r: crate::pac::radio::Radio, state: &'static crate::radio::State, needs_enable: bool, phantom: PhantomData<&'d ()>, } impl<'d> Radio<'d> { /// Create a new IEEE 802.15.4 radio driver. pub fn new( _radio: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { let r = crate::pac::RADIO; // Disable and enable to reset peripheral r.power().write(|w| w.set_power(false)); r.power().write(|w| w.set_power(true)); errata::post_power(); // 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 crate::interrupt::typelevel::RADIO::unpend(); unsafe { crate::interrupt::typelevel::RADIO::enable() }; let mut radio = Self { r: crate::pac::RADIO, state: T::state(), needs_enable: false, phantom: PhantomData, }; 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 = self.r; 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 = self.r; 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 = self.r; r.sfd().write(|w| w.set_sfd(sfd)); } /// Clear interrupts pub fn clear_all_interrupts(&mut self) { let r = self.r; r.intenclr().write(|w| w.0 = 0xffff_ffff); } /// Changes the radio transmission power pub fn set_transmission_power(&mut self, power: i8) { let r = self.r; 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 { self.r.state().read().state() } /// Moves the radio from any state to the DISABLED state fn disable(&mut self) { let r = self.r; // 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 = self.r; r.packetptr().write_value(buffer.as_ptr() as u32); } /// Moves the radio to the RXIDLE state fn receive_prepare(&mut self) { // clear related events self.r.events_ccabusy().write_value(0); self.r.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 = self.r; 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 = crate::pac::RADIO; r.shorts().write(|_| {}); r.tasks_stop().write_value(1); loop { match r.state().read().state() { 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 = self.state; let r = self.r; // 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 = self.state; let r = self.r; // 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); } mod errata { pub fn post_power() { // Workaround for anomaly 158 #[cfg(feature = "_nrf5340-net")] for i in 0..32 { let info = crate::pac::FICR.trimcnf(i); let addr = info.addr().read(); if addr & 0xFFFF_F000 == crate::pac::RADIO.as_ptr() as u32 { unsafe { (addr as *mut u32).write_volatile(info.data().read()); } } } } } ================================================ FILE: embassy-nrf/src/radio/mod.rs ================================================ //! 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. #[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_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; pub use pac::radio::vals::Txpower as TxPower; use crate::{interrupt, pac}; /// 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 } #[allow(unused)] 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: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } ================================================ FILE: embassy-nrf/src/reset.rs ================================================ //! 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)); } ================================================ FILE: embassy-nrf/src/rng.rs ================================================ //! 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::{Peri, PeripheralType}; use embassy_sync::waitqueue::WakerRegistration; use crate::interrupt::typelevel::Interrupt; use crate::mode::{Async, Blocking, Mode}; use crate::{interrupt, pac}; /// 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, M: Mode> { r: pac::rng::Rng, state: &'static State, _phantom: PhantomData<(&'d (), M)>, } impl<'d> Rng<'d, Blocking> { /// 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_blocking(_rng: Peri<'d, T>) -> Self { let this = Self { r: T::regs(), state: T::state(), _phantom: PhantomData, }; this.stop(); this } } impl<'d> Rng<'d, Async> { /// 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: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { let this = Self { r: T::regs(), state: T::state(), _phantom: PhantomData, }; this.stop(); this.disable_irq(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; this } fn enable_irq(&self) { self.r.intenset().write(|w| w.set_valrdy(true)); } fn disable_irq(&self) { self.r.intenclr().write(|w| w.set_valrdy(true)); } /// 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(); let state = self.state; // 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 = 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 = state.borrow_mut(cs); state.ptr = ptr::null_mut(); state.end = ptr::null_mut(); }); }); poll_fn(|cx| { critical_section::with(|cs| { let mut s = 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); } } impl<'d, M: Mode> Rng<'d, M> { fn stop(&self) { self.r.tasks_stop().write_value(1) } fn start(&self) { self.r.tasks_start().write_value(1) } /// 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) { self.r.config().write(|w| w.set_dercen(enable)) } /// 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 = self.r; while regs.events_valrdy().read() == 0 {} regs.events_valrdy().write_value(0); *byte = regs.value().read().value(); } self.stop(); } /// Generate a random u32 pub fn blocking_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) } /// Generate a random u64 pub fn blocking_next_u64(&mut self) -> u64 { let mut bytes = [0; 8]; self.blocking_fill_bytes(&mut bytes); u64::from_ne_bytes(bytes) } } impl<'d, M: Mode> Drop for Rng<'d, M> { fn drop(&mut self) { self.stop(); critical_section::with(|cs| { let mut state = self.state.borrow_mut(cs); state.ptr = ptr::null_mut(); state.end = ptr::null_mut(); }); } } impl<'d, M: Mode> rand_core_06::RngCore for Rng<'d, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } fn next_u32(&mut self) -> u32 { self.blocking_next_u32() } fn next_u64(&mut self) -> u64 { self.blocking_next_u64() } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { self.blocking_fill_bytes(dest); Ok(()) } } impl<'d, M: Mode> rand_core_06::CryptoRng for Rng<'d, M> {} impl<'d, M: Mode> rand_core_09::RngCore for Rng<'d, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } fn next_u32(&mut self) -> u32 { self.blocking_next_u32() } fn next_u64(&mut self) -> u64 { self.blocking_next_u64() } } impl<'d, M: Mode> rand_core_09::CryptoRng for Rng<'d, M> {} /// 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: SealedInstance + PeripheralType + '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; } }; } ================================================ FILE: embassy-nrf/src/rramc.rs ================================================ //! Resistive Random-Access Memory Controller driver. use core::marker::PhantomData; use core::{ptr, slice}; use embedded_storage::nor_flash::{ ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, }; use crate::peripherals::RRAMC; use crate::{Peri, pac}; /// Unbuffered RRAMC mode. pub struct Unbuffered; /// Buffered RRAMC mode. pub struct Buffered; trait SealedRramMode {} /// Operating modes for RRAMC #[allow(private_bounds)] pub trait RramMode: SealedRramMode {} impl SealedRramMode for Unbuffered {} impl RramMode for Unbuffered {} impl SealedRramMode for Buffered {} impl RramMode for Buffered {} // // Export Nvmc alias and page size for downstream compatibility // /// RRAM-backed `Nvmc` compatibile driver. pub type Nvmc<'d> = Rramc<'d, Unbuffered>; /// Emulated page size. RRAM does not use pages. This exists only for downstream compatibility. pub const PAGE_SIZE: usize = 4096; // In bytes, one line is 128 bits const WRITE_LINE_SIZE: usize = 16; /// Size of RRAM flash in bytes. pub const FLASH_SIZE: usize = crate::chip::FLASH_SIZE; /// Error type for RRAMC 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, } } } /// Resistive Random-Access Memory Controller (RRAMC) that implements the `embedded-storage` /// traits. pub struct Rramc<'d, MODE: RramMode> { _p: Peri<'d, RRAMC>, _d: PhantomData, } impl<'d> Rramc<'d, Unbuffered> { /// Create Rramc driver. pub fn new(_p: Peri<'d, RRAMC>) -> Rramc<'d, Unbuffered> { Self { _p, _d: PhantomData } } } impl<'d, const BUFFER_SIZE_BYTES: usize> Rramc<'d, Buffered> { /// Create Rramc driver. pub fn new_buffered(_p: Peri<'d, RRAMC>) -> Rramc<'d, Buffered> { assert!(BUFFER_SIZE_BYTES > 0 && BUFFER_SIZE_BYTES <= 512); Self { _p, _d: PhantomData } } } impl<'d, MODE: RramMode> Rramc<'d, MODE> { fn regs() -> pac::rramc::Rramc { pac::RRAMC } fn wait_ready(&mut self) { let p = Self::regs(); while !p.ready().read().ready() {} } fn enable_read(&self) { Self::regs().config().write(|w| w.set_wen(false)); } fn finish_write(&mut self) { self.enable_read(); self.wait_ready(); } } impl<'d> Rramc<'d, Unbuffered> { fn wait_ready_write(&mut self) { let p = Self::regs(); while !p.readynext().read().readynext() {} } fn enable_write(&self) { Self::regs().config().write(|w| { w.set_wen(true); w.set_writebufsize(pac::rramc::vals::Writebufsize::UNBUFFERED) }); } } impl<'d, const SIZE: usize> Rramc<'d, Buffered> { fn wait_ready_write(&mut self) { let p = Self::regs(); while !p.readynext().read().readynext() {} while !p.bufstatus().writebufempty().read().empty() {} } fn commit(&self) { let p = Self::regs(); p.tasks_commitwritebuf().write_value(1); while !p.bufstatus().writebufempty().read().empty() {} } fn enable_write(&self) { Self::regs().config().write(|w| { w.set_wen(true); w.set_writebufsize(pac::rramc::vals::Writebufsize::from_bits(SIZE as _)) }); } } // // RRAM is not NOR flash, but many crates require embedded-storage NorFlash traits. We therefore // implement the traits for downstream compatibility. // impl<'d> MultiwriteNorFlash for Rramc<'d, Unbuffered> {} impl<'d> ErrorType for Rramc<'d, Unbuffered> { type Error = Error; } impl<'d> ReadNorFlash for Rramc<'d, Unbuffered> { 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 Rramc<'d, Unbuffered> { const WRITE_SIZE: usize = WRITE_LINE_SIZE; const ERASE_SIZE: usize = PAGE_SIZE; // RRAM can overwrite in-place, so emulate page erases by overwriting the page bytes with 0xFF. 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 % Self::ERASE_SIZE != 0 || to as usize % Self::ERASE_SIZE != 0 { return Err(Error::Unaligned); } self.enable_write(); self.wait_ready(); // Treat each emulated page separately so callers can rely on post‑erase read‑back // returning 0xFF just like on real NOR flash. let buf = [0xFFu8; Self::WRITE_SIZE]; for page_addr in (from..to).step_by(Self::ERASE_SIZE) { let page_end = page_addr + Self::ERASE_SIZE as u32; for line_addr in (page_addr..page_end).step_by(Self::WRITE_SIZE) { unsafe { let src = buf.as_ptr() as *const u32; let dst = line_addr as *mut u32; for i in 0..(Self::WRITE_SIZE / 4) { core::ptr::write_volatile(dst.add(i), core::ptr::read_unaligned(src.add(i))); } } self.wait_ready_write(); } } self.finish_write(); 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 % Self::WRITE_SIZE != 0 || bytes.len() % Self::WRITE_SIZE != 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); if (i + 1) % (Self::WRITE_SIZE / 4) == 0 { self.wait_ready_write(); } } } self.enable_read(); self.wait_ready(); Ok(()) } } impl<'d, const BUFFER_SIZE_BYTES: usize> MultiwriteNorFlash for Rramc<'d, Buffered> {} impl<'d, const BUFFER_SIZE_BYTES: usize> ErrorType for Rramc<'d, Buffered> { type Error = Error; } impl<'d, const BUFFER_SIZE_BYTES: usize> ReadNorFlash for Rramc<'d, Buffered> { 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, const BUFFER_SIZE_BYTES: usize> NorFlash for Rramc<'d, Buffered> { const WRITE_SIZE: usize = WRITE_LINE_SIZE; const ERASE_SIZE: usize = PAGE_SIZE; // RRAM can overwrite in-place, so emulate page erases by overwriting the page bytes with 0xFF. 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 % Self::ERASE_SIZE != 0 || to as usize % Self::ERASE_SIZE != 0 { return Err(Error::Unaligned); } self.enable_write(); self.wait_ready(); // Treat each emulated page separately so callers can rely on post‑erase read‑back // returning 0xFF just like on real NOR flash. let buf = [0xFFu8; BUFFER_SIZE_BYTES]; for page_addr in (from..to).step_by(Self::ERASE_SIZE) { let page_end = page_addr + Self::ERASE_SIZE as u32; for line_addr in (page_addr..page_end).step_by(BUFFER_SIZE_BYTES) { unsafe { let src = buf.as_ptr() as *const u32; let dst = line_addr as *mut u32; for i in 0..(Self::WRITE_SIZE / 4) { core::ptr::write_volatile(dst.add(i), core::ptr::read_unaligned(src.add(i))); } } self.wait_ready_write(); } } self.commit(); self.finish_write(); 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 % Self::WRITE_SIZE != 0 || bytes.len() % Self::WRITE_SIZE != 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); if (i + 1) % (Self::WRITE_SIZE / 4) == 0 { self.wait_ready_write(); } } } self.commit(); self.enable_read(); self.wait_ready(); Ok(()) } } ================================================ FILE: embassy-nrf/src/rtc.rs ================================================ //! Low-level RTC driver. #![macro_use] use core::marker::PhantomData; use embassy_hal_internal::interrupt::InterruptExt; use embassy_hal_internal::{Peri, PeripheralType}; use crate::interrupt::typelevel::Interrupt as _; use crate::{interrupt, pac}; /// Prescaler has an invalid value which exceeds 12 bits. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PrescalerOutOfRangeError(u32); /// Compare value has an invalid value which exceeds 24 bits. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct CompareOutOfRangeError(u32); /// Interrupts/Events that can be generated by the RTCn peripheral. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Interrupt { /// Tick interrupt. Tick, /// Overflow interrupt. Overflow, /// Compare 0 interrupt. Compare0, /// Compare 1 interrupt. Compare1, /// Compare 2 interrupt. Compare2, /// Compare 3 interrupt. Only implemented for RTC1 and RTC2. Compare3, } /// Compare registers available on the RTCn. #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CompareChannel { /// Channel 0 _0, /// Channel 1 _1, /// Channel 2 _2, /// Channel 3. Only implemented for RTC1 and RTC2. _3, } pub(crate) trait SealedInstance { fn regs() -> pac::rtc::Rtc; } /// Basic RTC instance. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: crate::interrupt::typelevel::Interrupt; /// Unsafely create a peripheral instance. /// /// # Safety /// /// Potentially allows to create multiple instances of the driver for the same peripheral /// which can lead to undefined behavior. unsafe fn steal() -> Peri<'static, Self>; } macro_rules! impl_rtc { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::rtc::SealedInstance for peripherals::$type { #[inline] fn regs() -> pac::rtc::Rtc { unsafe { pac::rtc::Rtc::from_ptr(pac::$pac_type.as_ptr()) } } } impl crate::rtc::Instance for peripherals::$type { type Interrupt = crate::interrupt::typelevel::$irq; unsafe fn steal() -> embassy_hal_internal::Peri<'static, Self> { unsafe { peripherals::$type::steal() } } } }; } /// nRF RTC driver. pub struct Rtc<'d> { r: pac::rtc::Rtc, irq: interrupt::Interrupt, _phantom: PhantomData<&'d ()>, } impl<'d> Rtc<'d> { /// Create a new `Rtc` driver. /// /// fRTC \[Hz\] = 32_768 / (`prescaler` + 1 ) pub fn new(_rtc: Peri<'d, T>, prescaler: u32) -> Result { if prescaler >= (1 << 12) { return Err(PrescalerOutOfRangeError(prescaler)); } T::regs().prescaler().write(|w| w.set_prescaler(prescaler as u16)); Ok(Self { r: T::regs(), irq: T::Interrupt::IRQ, _phantom: PhantomData, }) } /// Create a new `Rtc` driver, configuring it to run at the given frequency. pub fn new_for_freq(rtc: Peri<'d, T>, freq_hz: u32) -> Result { let prescaler = (32_768 / freq_hz).saturating_sub(1); Self::new(rtc, prescaler) } /// Steal the RTC peripheral, without checking if it's already taken. /// /// # Safety /// /// Potentially allows to create multiple instances of the driver for the same peripheral /// which can lead to undefined behavior. pub unsafe fn steal() -> Self { Self { r: T::regs(), irq: T::Interrupt::IRQ, _phantom: PhantomData, } } /// Direct access to the RTC registers. #[cfg(feature = "unstable-pac")] #[inline] pub fn regs(&mut self) -> pac::rtc::Rtc { self.r } /// Enable the RTC. #[inline] pub fn enable(&mut self) { self.r.tasks_start().write_value(1); } /// Disable the RTC. #[inline] pub fn disable(&mut self) { self.r.tasks_stop().write_value(1); } /// Enables interrupts for the given [Interrupt] source. /// /// Optionally also enables the interrupt in the NVIC. pub fn enable_interrupt(&mut self, int: Interrupt, enable_in_nvic: bool) { let regs = self.r; match int { Interrupt::Tick => regs.intenset().write(|w| w.set_tick(true)), Interrupt::Overflow => regs.intenset().write(|w| w.set_ovrflw(true)), Interrupt::Compare0 => regs.intenset().write(|w| w.set_compare(0, true)), Interrupt::Compare1 => regs.intenset().write(|w| w.set_compare(1, true)), Interrupt::Compare2 => regs.intenset().write(|w| w.set_compare(2, true)), Interrupt::Compare3 => regs.intenset().write(|w| w.set_compare(3, true)), } if enable_in_nvic { unsafe { self.irq.enable() }; } } /// Disables interrupts for the given [Interrupt] source. /// /// Optionally also disables the interrupt in the NVIC. pub fn disable_interrupt(&mut self, int: Interrupt, disable_in_nvic: bool) { let regs = self.r; match int { Interrupt::Tick => regs.intenclr().write(|w| w.set_tick(true)), Interrupt::Overflow => regs.intenclr().write(|w| w.set_ovrflw(true)), Interrupt::Compare0 => regs.intenclr().write(|w| w.set_compare(0, true)), Interrupt::Compare1 => regs.intenclr().write(|w| w.set_compare(1, true)), Interrupt::Compare2 => regs.intenclr().write(|w| w.set_compare(2, true)), Interrupt::Compare3 => regs.intenclr().write(|w| w.set_compare(3, true)), } if disable_in_nvic { self.irq.disable(); } } /// Enable the generation of a hardware event from a given stimulus. pub fn enable_event(&mut self, evt: Interrupt) { let regs = self.r; match evt { Interrupt::Tick => regs.evtenset().write(|w| w.set_tick(true)), Interrupt::Overflow => regs.evtenset().write(|w| w.set_ovrflw(true)), Interrupt::Compare0 => regs.evtenset().write(|w| w.set_compare(0, true)), Interrupt::Compare1 => regs.evtenset().write(|w| w.set_compare(1, true)), Interrupt::Compare2 => regs.evtenset().write(|w| w.set_compare(2, true)), Interrupt::Compare3 => regs.evtenset().write(|w| w.set_compare(3, true)), } } /// Disable the generation of a hardware event from a given stimulus. pub fn disable_event(&mut self, evt: Interrupt) { let regs = self.r; match evt { Interrupt::Tick => regs.evtenclr().write(|w| w.set_tick(true)), Interrupt::Overflow => regs.evtenclr().write(|w| w.set_ovrflw(true)), Interrupt::Compare0 => regs.evtenclr().write(|w| w.set_compare(0, true)), Interrupt::Compare1 => regs.evtenclr().write(|w| w.set_compare(1, true)), Interrupt::Compare2 => regs.evtenclr().write(|w| w.set_compare(2, true)), Interrupt::Compare3 => regs.evtenclr().write(|w| w.set_compare(3, true)), } } /// Resets the given event. pub fn reset_event(&mut self, evt: Interrupt) { let regs = self.r; match evt { Interrupt::Tick => regs.events_tick().write_value(0), Interrupt::Overflow => regs.events_ovrflw().write_value(0), Interrupt::Compare0 => regs.events_compare(0).write_value(0), Interrupt::Compare1 => regs.events_compare(1).write_value(0), Interrupt::Compare2 => regs.events_compare(2).write_value(0), Interrupt::Compare3 => regs.events_compare(3).write_value(0), } } /// Checks if the given event has been triggered. pub fn is_event_triggered(&self, evt: Interrupt) -> bool { let regs = self.r; let val = match evt { Interrupt::Tick => regs.events_tick().read(), Interrupt::Overflow => regs.events_ovrflw().read(), Interrupt::Compare0 => regs.events_compare(0).read(), Interrupt::Compare1 => regs.events_compare(1).read(), Interrupt::Compare2 => regs.events_compare(2).read(), Interrupt::Compare3 => regs.events_compare(3).read(), }; val == 1 } /// Set the compare value of a given register. The compare registers have a width /// of 24 bits. pub fn set_compare(&mut self, reg: CompareChannel, val: u32) -> Result<(), CompareOutOfRangeError> { if val >= (1 << 24) { return Err(CompareOutOfRangeError(val)); } let reg = match reg { CompareChannel::_0 => 0, CompareChannel::_1 => 1, CompareChannel::_2 => 2, CompareChannel::_3 => 3, }; self.r.cc(reg).write(|w| w.set_compare(val)); Ok(()) } /// Clear the Real Time Counter. #[inline] pub fn clear(&self) { self.r.tasks_clear().write_value(1); } /// Obtain the current value of the Real Time Counter, 24 bits of range. #[inline] pub fn read(&self) -> u32 { self.r.counter().read().counter() } } ================================================ FILE: embassy-nrf/src/saadc.rs ================================================ //! Successive Approximation Analog-to-Digital Converter (SAADC) driver. #![macro_use] use core::future::poll_fn; use core::marker::PhantomData; use core::sync::atomic::{Ordering, compiler_fence}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(feature = "_nrf54l"))] 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}; /// 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. #[cfg(not(feature = "_nrf54l"))] pub resistor: Resistor, /// Acquisition time in microseconds. pub time: Time, /// Positive channel to sample p_channel: AnyInput<'d>, /// 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 Input + 'd) -> Self { Self { reference: Reference::INTERNAL, #[cfg(not(feature = "_nrf54l"))] gain: Gain::GAIN1_6, #[cfg(feature = "_nrf54l")] gain: Gain::GAIN2_8, #[cfg(not(feature = "_nrf54l"))] resistor: Resistor::BYPASS, time: Time::_10US, p_channel: input.degrade_saadc(), n_channel: None, } } /// Default configuration for differential channel sampling. pub fn differential(p_input: impl Input + 'd, n_input: impl Input + 'd) -> Self { Self { reference: Reference::INTERNAL, #[cfg(not(feature = "_nrf54l"))] gain: Gain::GAIN1_6, #[cfg(feature = "_nrf54l")] gain: Gain::GAIN2_8, #[cfg(not(feature = "_nrf54l"))] resistor: Resistor::BYPASS, time: Time::_10US, p_channel: p_input.degrade_saadc(), n_channel: Some(n_input.degrade_saadc()), } } } const CNT_UNIT: usize = if cfg!(feature = "_nrf54l") { 2 } else { 1 }; /// 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: Peri<'d, peripherals::SAADC>, } impl<'d, const N: usize> Saadc<'d, N> { /// Create a new SAADC driver. pub fn new( saadc: Peri<'d, peripherals::SAADC>, _irq: impl interrupt::typelevel::Binding + 'd, config: Config, channel_configs: [ChannelConfig; N], ) -> Self { 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() { #[cfg(not(feature = "_nrf54l"))] r.ch(i).pselp().write(|w| w.set_pselp(cc.p_channel.channel())); #[cfg(feature = "_nrf54l")] r.ch(i).pselp().write(|w| { w.set_port(cc.p_channel.port()); w.set_pin(cc.p_channel.pin()); w.set_internal(cc.p_channel.internal()); w.set_connect(cc.p_channel.connect()); }); if let Some(n_channel) = &cc.n_channel { #[cfg(not(feature = "_nrf54l"))] r.ch(i).pseln().write(|w| w.set_pseln(n_channel.channel())); #[cfg(feature = "_nrf54l")] r.ch(i).pseln().write(|w| { w.set_port(n_channel.port()); w.set_pin(n_channel.pin()); w.set_connect(n_channel.connect().to_bits().into()); }); } r.ch(i).config().write(|w| { w.set_refsel(cc.reference.into()); w.set_gain(cc.gain.into()); w.set_tacq(cc.time.into()); #[cfg(feature = "_nrf54l")] w.set_tconv(7); // 7 is the default from the Nordic C driver w.set_mode(match cc.n_channel { None => vals::ConfigMode::SE, Some(_) => vals::ConfigMode::DIFF, }); #[cfg(not(feature = "_nrf54l"))] w.set_resp(cc.resistor.into()); #[cfg(not(feature = "_nrf54l"))] w.set_resn(vals::Resn::BYPASS); #[cfg(not(feature = "_nrf54lm20"))] w.set_burst(!matches!(oversample, Oversample::BYPASS)); }); #[cfg(feature = "_nrf54lm20")] r.burst() .write(|w| 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 * CNT_UNIT) 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: Peri<'_, T>, ppi_ch1: Peri<'_, impl ConfigurableChannel>, ppi_ch2: Peri<'_, 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 * CNT_UNIT) 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) { // Reset of SAADC. // // This is needed when more than one pin is sampled to avoid needless power consumption. // More information can be found in [nrf52 Anomaly 241](https://docs.nordicsemi.com/bundle/errata_nRF52810_Rev1/page/ERR/nRF52810/Rev1/latest/anomaly_810_241.html). // The workaround seems like it copies the configuration before reset and reapplies it after. // The instance is dropped, forcing a reconfiguration at compile time, hence we only // call what is the reset portion of the workaround. #[cfg(feature = "_nrf52")] { unsafe { core::ptr::write_volatile(0x40007FFC as *mut u32, 0) } unsafe { core::ptr::read_volatile(0x40007FFC as *const ()) } unsafe { core::ptr::write_volatile(0x40007FFC as *mut u32, 1) } } let r = Self::regs(); r.enable().write(|w| w.set_enable(false)); for i in 0..N { #[cfg(not(feature = "_nrf54l"))] { r.ch(i).pselp().write(|w| w.set_pselp(InputChannel::NC)); r.ch(i).pseln().write(|w| w.set_pseln(InputChannel::NC)); } #[cfg(feature = "_nrf54l")] { r.ch(i).pselp().write(|w| w.set_connect(vals::PselpConnect::NC)); r.ch(i).pseln().write(|w| w.set_connect(vals::PselnConnect::NC)); } } } } #[cfg(not(feature = "_nrf54l"))] 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, } } } #[cfg(feature = "_nrf54l")] impl From for vals::Gain { fn from(gain: Gain) -> Self { match gain { Gain::GAIN2_8 => vals::Gain::GAIN2_8, Gain::GAIN2_7 => vals::Gain::GAIN2_7, Gain::GAIN2_6 => vals::Gain::GAIN2_6, Gain::GAIN2_5 => vals::Gain::GAIN2_5, Gain::GAIN2_4 => vals::Gain::GAIN2_4, Gain::GAIN2_3 => vals::Gain::GAIN2_3, Gain::GAIN1 => vals::Gain::GAIN1, Gain::GAIN2 => vals::Gain::GAIN2, } } } /// Gain control #[cfg(not(feature = "_nrf54l"))] #[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, } /// Gain control #[cfg(feature = "_nrf54l")] #[non_exhaustive] #[derive(Clone, Copy)] pub enum Gain { /// 2/8 GAIN2_8 = 0, /// 2/7 GAIN2_7 = 1, /// 2/6 GAIN2_6 = 2, /// 2/5 GAIN2_5 = 3, /// 2/4 GAIN2_4 = 4, /// 2/3 GAIN2_3 = 5, /// 1 GAIN1 = 6, /// 2 GAIN2 = 7, } impl From for vals::Refsel { fn from(reference: Reference) -> Self { match reference { Reference::INTERNAL => vals::Refsel::INTERNAL, #[cfg(not(feature = "_nrf54l"))] Reference::VDD1_4 => vals::Refsel::VDD1_4, #[cfg(feature = "_nrf54l")] Reference::EXTERNAL => vals::Refsel::EXTERNAL, } } } /// Reference control #[non_exhaustive] #[derive(Clone, Copy)] pub enum Reference { /// Internal reference (0.6 V) INTERNAL = 0, #[cfg(not(feature = "_nrf54l"))] /// VDD/4 as reference VDD1_4 = 1, /// PADC_EXT_REF_1V2 as reference #[cfg(feature = "_nrf54l")] EXTERNAL = 1, } #[cfg(not(feature = "_nrf54l"))] 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)] #[cfg(not(feature = "_nrf54l"))] 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, } #[cfg(not(feature = "_nrf54l"))] impl From

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.