├── .cargo └── config.toml ├── .config ├── lingo.dic └── spellcheck.toml ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── Cargo.toml ├── examples │ ├── buzzer.rs │ ├── clock.rs │ ├── gpio-blink.rs │ ├── gpio-button-irq.rs │ ├── gpio-button.rs │ └── info.rs └── src │ └── lib.rs ├── hal ├── Cargo.toml └── src │ ├── adc.rs │ ├── aes.rs │ ├── dac.rs │ ├── dma │ ├── cr.rs │ └── mod.rs │ ├── flash.rs │ ├── fmt.rs │ ├── gpio.rs │ ├── i2c.rs │ ├── info.rs │ ├── lib.rs │ ├── lptim │ ├── cfgr.rs │ ├── cr.rs │ └── mod.rs │ ├── macros.rs │ ├── pka.rs │ ├── pwr.rs │ ├── ratio.rs │ ├── rcc.rs │ ├── rng.rs │ ├── rtc │ ├── alarm.rs │ └── mod.rs │ ├── spi.rs │ ├── subghz │ ├── bit_sync.rs │ ├── cad_params.rs │ ├── calibrate.rs │ ├── fallback_mode.rs │ ├── hse_trim.rs │ ├── irq.rs │ ├── lora_sync_word.rs │ ├── mod.rs │ ├── mod_params.rs │ ├── ocp.rs │ ├── op_error.rs │ ├── pa_config.rs │ ├── packet_params.rs │ ├── packet_status.rs │ ├── packet_type.rs │ ├── pkt_ctrl.rs │ ├── pmode.rs │ ├── pwr_ctrl.rs │ ├── reg_mode.rs │ ├── rf_frequency.rs │ ├── rx_timeout_stop.rs │ ├── sleep_cfg.rs │ ├── smps.rs │ ├── standby_clk.rs │ ├── stats.rs │ ├── status.rs │ ├── tcxo_mode.rs │ ├── timeout.rs │ ├── tx_params.rs │ └── value_error.rs │ ├── uart.rs │ └── util.rs ├── lora-e5-bsp ├── Cargo.toml ├── README.md └── src │ ├── led.rs │ ├── lib.rs │ └── pb.rs ├── memory.x ├── nucleo-wl55jc-bsp ├── Cargo.toml ├── README.md └── src │ ├── led.rs │ ├── lib.rs │ └── pb.rs ├── rustfmt.toml └── testsuite ├── Cargo.toml ├── README.md ├── runall.py └── src ├── adc.rs ├── aes.rs ├── dac.rs ├── flash.rs ├── i2c.rs ├── info.rs ├── lptim.rs ├── pka.rs ├── rcc.rs ├── rng.rs ├── rtc.rs ├── spi.rs ├── subghz.rs └── uart.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32WLE5JCIx --connect-under-reset" 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x", 5 | "-C", "link-arg=-Tdefmt.x", 6 | ] 7 | 8 | [alias] 9 | # e.g. cargo run-ex gpio-blink 10 | run-ex = "run -p examples --target thumbv7em-none-eabi --example" 11 | 12 | # e.g. 13 | # cargo test-aes 14 | # cargo test-subghz -- --probe 001D00145553500A20393256 15 | # cargo test-subghz -- --probe 001600345553500A20393256 16 | test-adc = "test -p testsuite --target thumbv7em-none-eabi --bin adc" 17 | test-aes = "test -p testsuite --target thumbv7em-none-eabi --bin aes" 18 | test-dac = "test -p testsuite --target thumbv7em-none-eabi --bin dac" 19 | test-flash = "test -p testsuite --target thumbv7em-none-eabi --bin flash" 20 | test-i2c = "test -p testsuite --target thumbv7em-none-eabi --bin i2c" 21 | test-info = "test -p testsuite --target thumbv7em-none-eabi --bin info" 22 | test-lptim = "test -p testsuite --target thumbv7em-none-eabi --bin lptim" 23 | test-pka = "test -p testsuite --target thumbv7em-none-eabi --bin pka" 24 | test-rcc = "test -p testsuite --target thumbv7em-none-eabi --bin rcc" 25 | test-rng = "test -p testsuite --target thumbv7em-none-eabi --bin rng" 26 | test-rtc = "test -p testsuite --target thumbv7em-none-eabi --bin rtc" 27 | test-spi = "test -p testsuite --target thumbv7em-none-eabi --bin spi" 28 | test-subghz = "test -p testsuite --target thumbv7em-none-eabi --bin subghz" 29 | test-uart = "test -p testsuite --target thumbv7em-none-eabi --bin uart" 30 | 31 | # e.g. cargo unit 32 | unit = "test --features stm32wl5x_cm4,chrono,embedded-time" 33 | unit-little = "test --features stm32wl5x_cm0p,chrono,embedded-time" 34 | unit-nucleo = "test -p nucleo-wl55jc-bsp --features stm32wl5x_cm4" 35 | unit-lora-e5 = "test -p lora-e5-bsp" 36 | 37 | [env] 38 | DEFMT_LOG = "trace" 39 | -------------------------------------------------------------------------------- /.config/lingo.dic: -------------------------------------------------------------------------------- 1 | 100 2 | AES 3 | AHB 4 | autoreload 5 | bitmask 6 | bitrate 7 | BPSK 8 | brainpool 9 | BT 10 | codebook 11 | COMP1_OUT 12 | COMP2_OUT 13 | CRC 14 | cryptographically 15 | D0 16 | D5 17 | DAC 18 | datasheet 19 | dBm 20 | decrypt 21 | despreading 22 | detections 23 | DMA 24 | DMA1 25 | DMA2 26 | DMAMUX 27 | DS13293 28 | durations 29 | ECB 30 | ECC 31 | ECDSA 32 | EXTI 33 | fn 34 | FS 35 | FSK 36 | Functionalities 37 | GCM 38 | GFSK 39 | GPIO 40 | GPIOA 41 | GPIOB 42 | GPIOs 43 | hal 44 | HALs 45 | HCLK3 46 | HSE 47 | HSE32 48 | HSI 49 | HW 50 | IEEE 51 | IRQ 52 | IRQs 53 | ISR 54 | jitter 55 | kibibytes 56 | LED1 57 | LED2 58 | LED3 59 | LEDs 60 | LoRa 61 | LPTIM 62 | LPTIM_ICR 63 | LPTIM_ISR 64 | LPTIM1 65 | LPTIM1_OUT 66 | LPTIM2 67 | LPTIM2_OUT 68 | LPTIM3 69 | LPUART 70 | LSE 71 | LSI 72 | mA 73 | milliamps 74 | millivolts 75 | MISO 76 | MOSI 77 | MSI 78 | MSK 79 | mV 80 | nist 81 | NSS 82 | NUCLEO 83 | NVIC 84 | opcode 85 | PB3 86 | pF 87 | PKA 88 | PLL 89 | pointee 90 | prescaler 91 | prescalers 92 | PWM 93 | Quickstart 94 | ramping 95 | RM0453 96 | RNG 97 | RSA 98 | RSSI 99 | RTC 100 | runtime 101 | SCK 102 | seeed 103 | SMPS 104 | SNR 105 | SPI 106 | SRAM 107 | startup 108 | STM32WL 109 | STM32WL5X 110 | struct 111 | subghz 112 | SubGhz 113 | SubGHz 114 | SUBGHZ 115 | syncword 116 | sysclk 117 | systick 118 | TAMP1 119 | TAMP2 120 | TAMP3 121 | TCXO 122 | timestamp 123 | Timestamp 124 | tuple 125 | UART 126 | UFBGA73 127 | UFQFPN48 128 | UID64 129 | uncalibrated 130 | unmapped 131 | VBAT 132 | wakeup 133 | WFE 134 | WFI 135 | WLCSP59 136 | -------------------------------------------------------------------------------- /.config/spellcheck.toml: -------------------------------------------------------------------------------- 1 | # see https://github.com/drahnr/cargo-spellcheck 2 | [Hunspell] 3 | lang = "en_US" 4 | 5 | search_dirs = ["."] 6 | extra_dictionaries = ["lingo.dic"] 7 | 8 | skip_os_lookups = true 9 | use_builtin = true 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | pull_request: 6 | # allows manual triggering 7 | workflow_dispatch: 8 | merge_group: 9 | 10 | name: CI 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | env: {"RUSTFLAGS": "-D warnings"} 17 | strategy: 18 | matrix: 19 | target: 20 | - "thumbv6m-none-eabi" 21 | - "thumbv7em-none-eabi" 22 | mcu: 23 | - "stm32wl5x_cm0p" 24 | - "stm32wl5x_cm4" 25 | - "stm32wle5" 26 | toolchain: 27 | - "1.85" # MSRV 28 | - "beta" 29 | exclude: 30 | - mcu: "stm32wl5x_cm0p" 31 | target: "thumbv7em-none-eabi" 32 | - mcu: "stm32wl5x_cm4" 33 | target: "thumbv6m-none-eabi" 34 | - mcu: "stm32wle5" 35 | target: "thumbv6m-none-eabi" 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: dtolnay/rust-toolchain@master 39 | with: 40 | toolchain: ${{ matrix.toolchain }} 41 | target: ${{ matrix.target }} 42 | 43 | - name: Build HAL 44 | run: | 45 | cargo build \ 46 | --target ${{ matrix.target }} \ 47 | --features ${{ matrix.mcu }} 48 | 49 | - name: Build NUCLEO BSP 50 | if: ${{ startsWith(matrix.mcu, 'stm32wl5x') }} 51 | run: | 52 | cargo build \ 53 | --package nucleo-wl55jc-bsp \ 54 | --target ${{ matrix.target }} \ 55 | --features ${{ matrix.mcu }},defmt 56 | 57 | - name: Build LoRa E5 BSP 58 | if: ${{ matrix.mcu == 'stm32wle5' }} 59 | run: | 60 | cargo build \ 61 | --package lora-e5-bsp \ 62 | --target ${{ matrix.target }} \ 63 | --features defmt 64 | 65 | - name: Build Examples 66 | if: ${{ matrix.mcu == 'stm32wl5x_cm4' }} 67 | run: cargo build --examples -p examples --target ${{ matrix.target }} 68 | 69 | build_testsuite: 70 | name: Build Testsuite 71 | runs-on: ubuntu-latest 72 | env: 73 | # setting this overrides environment variables used for linking 74 | # RUSTFLAGS: "-D warnings" 75 | DEFMT_LOG: "debug" 76 | steps: 77 | - uses: actions/checkout@v4 78 | - uses: dtolnay/rust-toolchain@stable 79 | with: 80 | target: thumbv7em-none-eabi 81 | - run: | 82 | cargo test -p testsuite --target thumbv7em-none-eabi --bins --locked --no-run 83 | - name: Upload test binaries 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: test-bins 87 | if-no-files-found: error 88 | retention-days: 1 89 | path: | 90 | target/thumbv7em-none-eabi/debug/deps/* 91 | !target/thumbv7em-none-eabi/debug/deps/*.cargo-lock 92 | !target/thumbv7em-none-eabi/debug/deps/*.d 93 | !target/thumbv7em-none-eabi/debug/deps/*.meta 94 | !target/thumbv7em-none-eabi/debug/deps/*.rlib 95 | !target/thumbv7em-none-eabi/debug/deps/*.rmeta 96 | !target/thumbv7em-none-eabi/debug/deps/.fingerprint/**/* 97 | !target/thumbv7em-none-eabi/debug/deps/build/**/* 98 | !target/thumbv7em-none-eabi/debug/deps/deps/**/* 99 | !target/thumbv7em-none-eabi/debug/deps/examples/**/* 100 | !target/thumbv7em-none-eabi/debug/deps/incremental/**/* 101 | 102 | test: 103 | name: Unit Tests 104 | runs-on: ubuntu-latest 105 | env: {"RUSTFLAGS": "-D warnings"} 106 | strategy: 107 | matrix: 108 | mcu: 109 | - "stm32wl5x_cm0p" 110 | - "stm32wl5x_cm4" 111 | - "stm32wle5" 112 | steps: 113 | - uses: actions/checkout@v4 114 | - uses: dtolnay/rust-toolchain@stable 115 | 116 | - name: Test HAL 117 | run: cargo test --features ${{ matrix.mcu }},embedded-time,chrono 118 | 119 | - name: Test nucleo BSP 120 | if: ${{ startsWith(matrix.mcu, 'stm32wl5x') }} 121 | run: | 122 | cargo test -p nucleo-wl55jc-bsp --features ${{ matrix.mcu }},embedded-time,chrono 123 | 124 | - name: Test LoRa E5 BSP 125 | if: ${{ matrix.mcu == 'stm32wle5' }} 126 | run: cargo test -p lora-e5-bsp --features embedded-time,chrono 127 | 128 | clippy: 129 | name: Clippy 130 | runs-on: ubuntu-latest 131 | steps: 132 | - uses: actions/checkout@v4 133 | - uses: dtolnay/rust-toolchain@stable 134 | with: 135 | components: clippy 136 | - run: cargo clippy --features stm32wl5x_cm4 -- --deny warnings 137 | 138 | format: 139 | name: Format 140 | runs-on: ubuntu-latest 141 | env: {"RUSTFLAGS": "-D warnings"} 142 | steps: 143 | - uses: actions/checkout@v4 144 | - uses: dtolnay/rust-toolchain@nightly 145 | with: 146 | components: rustfmt 147 | - run: cargo +nightly fmt -- --check 148 | 149 | doc: 150 | name: doc 151 | runs-on: ubuntu-latest 152 | env: {"RUSTDOCFLAGS": "-D warnings"} 153 | steps: 154 | - uses: actions/checkout@v4 155 | - uses: dtolnay/rust-toolchain@stable 156 | # not run in parallel to avoid hitting concurrency limit 157 | # stm32wl5x_cm4 covered by rustdoc 158 | - run: cargo doc --features stm32wl5x_cm0p 159 | - run: cargo doc --features stm32wle5 160 | 161 | rustdoc: 162 | name: rustdoc 163 | runs-on: ubuntu-latest 164 | env: {"RUSTDOCFLAGS": "-D warnings --cfg docsrs"} 165 | permissions: 166 | contents: write 167 | steps: 168 | - uses: actions/checkout@v4 169 | - uses: dtolnay/rust-toolchain@nightly 170 | - name: rustdoc 171 | run: | 172 | cd hal 173 | cargo +nightly rustdoc \ 174 | --features chrono,embedded-time,rt,stm32wl5x_cm4 \ 175 | -- -Z unstable-options --enable-index-page 176 | chmod -R 777 ../target 177 | - name: Upload artifact 178 | uses: actions/upload-pages-artifact@v3 179 | with: 180 | path: target/doc 181 | 182 | deploy_rustdoc: 183 | name: Deploy Nightly Docs 184 | runs-on: ubuntu-latest 185 | needs: rustdoc 186 | if: ${{ github.ref == 'refs/heads/main' }} 187 | permissions: 188 | pages: write 189 | id-token: write 190 | environment: 191 | name: github-pages 192 | url: ${{ steps.deployment.outputs.page_url }} 193 | steps: 194 | - name: Deploy to GitHub Pages 195 | uses: actions/deploy-pages@v4 196 | id: deployment 197 | 198 | release: 199 | name: crates.io release 200 | if: startsWith(github.ref, 'refs/tags/v') 201 | needs: 202 | - build 203 | - build_testsuite 204 | - clippy 205 | - doc 206 | - format 207 | - rustdoc 208 | - test 209 | runs-on: ubuntu-latest 210 | steps: 211 | - uses: actions/checkout@v4 212 | - uses: dtolnay/rust-toolchain@stable 213 | - name: Release stm32wlxx-hal 214 | run: | 215 | cd hal 216 | cargo publish --no-verify --token ${CRATES_IO_TOKEN} 217 | cd .. 218 | env: 219 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 220 | - name: Release nucleo-wl55jc-bsp 221 | run: | 222 | cd nucleo-wl55jc-bsp 223 | cargo publish --no-verify --token ${CRATES_IO_TOKEN} 224 | cd .. 225 | env: 226 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 227 | - name: Release lora-e5-bsp 228 | run: | 229 | cd lora-e5-bsp 230 | cargo publish --no-verify --token ${CRATES_IO_TOKEN} 231 | cd .. 232 | env: 233 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 234 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.pdf 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["rust-lang.rust-analyzer"] 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": ["stm32wl5x_cm4", "rt", "chrono"], 3 | "rust-analyzer.checkOnSave.features": ["stm32wl5x_cm4", "rt", "chrono"], 4 | "rust-analyzer.checkOnSave.allTargets": false, 5 | "rust-analyzer.cargo.target": "thumbv7em-none-eabi", 6 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - Added a missing `must_use` in `SleepCfg::set_startup`. 10 | 11 | ### Changed 12 | - Updated minimum `chrono` version to `0.4.23` to satisfy `cargo-audit`. 13 | - Updated defmt from `0.3` to `1`. 14 | - Updated `rand_core` `0.6` to `0.9`. 15 | - `stm32wlxx_hal::rng::Rng` now implements `rand_core::TryRngCore` instead of `rand_core::RngCore`. 16 | - `stm32wlxx_hal::rng::Rng` now implements `rand_core::TryCryptoRng` instead of `rand_core::CryptoRng`. 17 | - Changed the edition from 2021 to 2024. 18 | - Changed minimum supported rust version from 1.60 to 1.85. 19 | 20 | ### Fixed 21 | - Fixed an incorrect cast from `u8` to `i16` in `LoRaPacketStatus::snr_pkt` 22 | - Renamed function `TcxoMode::set_txco_trim` to `TcxoMode::set_tcxo_trim` to fix spelling. 23 | - Renamed enum `CmdStatus::Avaliable` to `CmdStatus::Available` to fix spelling. 24 | 25 | ## [0.6.1] - 2022-08-01 26 | ### Fixed 27 | - Fixed undefined behavior in SPI full duplex RX DMA transfers. 28 | 29 | ## [0.6.0] - 2022-07-04 30 | ### Added 31 | - Added `rcc::Lsco` to use the low-speed oscillator output. 32 | - Added `Rtc::calibrate_lp` to calibrate the RTC. 33 | - Added `Rtc::recalibration_pending`. 34 | - Added `Rtc::is_alarm_{a,b}_en`. 35 | - Added methods to utilize ADC oversampling. 36 | 37 | ### Changed 38 | - `Rtc.alarm_{a,b}` returns `Alarm` instead of `Option`. 39 | - Updated `stm32-rs` from `0.14.0` to `0.15.1`. 40 | 41 | ### Fixed 42 | - Fixed a documentation bug in `rtc::Alarm`. Values are masked if `true`, but the documentation indicated they are masked if `false`. 43 | 44 | ## [0.5.1] - 2022-05-14 45 | ### Added 46 | - Added `Rtc::alarm_{a,b}` to get the alarm value. 47 | - Added `impl From for chrono::NaiveTime`. 48 | - Added `RfSwitch::steal()` to all BSPs. 49 | 50 | ## [0.5.0] - 2022-05-08 51 | ### Added 52 | - Added `set_sleep_clock` to GPIO ports to enable and disable clocks during sleep. 53 | - Added `subghz::Timeout::saturating_add`. 54 | - Added `SubGhz.set_rtc_period` and `SubGhz.restart_rtc` methods required to workaround an erratum with `SubGhz.set_rx_duty_cycle`. 55 | - Added `SubGhz.new_no_reset` and `SubGhz.new_with_dma_no_reset` to create a `SubGhz` without resetting the radio. 56 | 57 | ### Changed 58 | - Changed minimum supported rust version from 1.57 to 1.60. 59 | 60 | ### Fixed 61 | - Fixed a typo in the `Exti::set_falling_trigger` function name. 62 | - Fixed endianness in `SubGhz.op_error`. 63 | 64 | ## [0.4.1] - 2022-03-22 65 | ### Added 66 | - Implement `embedded_hal::PwmPin` for `LpTim`. 67 | 68 | ### Changed 69 | - Inlined trivial `Rng` methods. 70 | 71 | ### Fixed 72 | - Fixed UART `clock_hz` methods returning the wrong frequency. 73 | 74 | ## [0.4.0] - 2022-01-08 75 | ### Added 76 | - Added a `is_pending` method to the `gpio::Exti` trait. 77 | - Added alarm functionality to the RTC. 78 | - Added `Rtc.is_wakeup_timer_en`. 79 | - Added `flash::flash_range`. 80 | - Added `Flash.program_bytes` for safer flash programming. 81 | 82 | ### Changed 83 | - Replaced `Debug` implementation with `Display` implementation for: 84 | - `subghz::FskPacketStatus` 85 | - `subghz::LoRaPacketStatus` 86 | - `subghz::Stats` 87 | - `subghz::Status` 88 | - `Flash::standard_program_generic` now checks for overflow. 89 | 90 | ### Fixed 91 | - Fixed an off-by-one error in `flash::Page::addr_range`. 92 | 93 | ### Removed 94 | - Removed `util::reset_cycle_count`; this functionality is now in `cortex-m`. 95 | 96 | ## [0.3.0] - 2021-12-20 97 | ### Added 98 | - Added `info::Core::CT` to get the CPU core at compile time. 99 | - Added `info::Core::from_cpuid()` to get the CPU core at runtime. 100 | - Added a `flash` module with erase and program functionality. 101 | - Added `defmt::Format` for all types declared in the BSPs. 102 | - Added `info::uid::PTR`. 103 | 104 | ### Changed 105 | - Changed minimum rust version from 1.56 to 1.57 for `const_panic`. 106 | - `info::UID64` 107 | - Moved to `info::Uid64::PTR`. 108 | - Changed the type from `*const u8` to `*const u32`. 109 | - Moved functions in `info` into the associated structs/enums. 110 | - Moved `info::uid64` to `info::Uid64::from_device`. 111 | - Moved `info::uid64_devnum` to `info::Uid64::read_devnum`. 112 | - Moved `info::package` to `info::Package::from_device`. 113 | - Moved `info::uid` to `info::Uid::from_device`. 114 | - Added `#[inline]` to `util::new_delay` and `util::reset_cycle_count`. 115 | - Large dependencies are now optional. 116 | - `embedded-time` is now an optional feature. 117 | - Changed `I2C::new` to use `u32` instead of `embedded_time::Hertz`. 118 | - `chrono` is now an optional feature. 119 | 120 | ## [0.2.1] - 2021-11-20 121 | ### Fixed 122 | - Fixed timeouts after calling `SubGhz::set_sleep`. 123 | 124 | ## [0.2.0] - 2021-11-11 125 | ### Added 126 | - Added two board support crates 127 | - `nucleo-wl55jc-bsp` 128 | - `lora-e5-bsp` 129 | 130 | ### Changed 131 | - Crate ownership changed from [tweedegolf] to [stm32-rs]. 132 | - Thank you [tweedegolf] for the initial development effort! 133 | - Sweeping changes throughout the entire crate, nothing is the same. 134 | 135 | ## [0.1.0] - 2021-03-26 136 | - Initial release by [tweedegolf] 137 | 138 | [tweedegolf]: https://github.com/tweedegolf 139 | [stm32-rs]: https://github.com/stm32-rs 140 | [Unreleased]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.6.1...HEAD 141 | [0.6.1]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.6.0...v0.6.1 142 | [0.6.0]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.5.1...v0.6.0 143 | [0.5.1]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.5.0...v0.5.1 144 | [0.5.0]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.4.1...v0.5.0 145 | [0.4.1]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.4.0...v0.4.1 146 | [0.4.0]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.3.0...v0.4.0 147 | [0.3.0]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.2.1...v0.3.0 148 | [0.2.1]: https://github.com/stm32-rs/stm32wlxx-hal/compare/v0.2.0...v0.2.1 149 | [0.2.0]: https://github.com/stm32-rs/stm32wlxx-hal/releases/tag/v0.2.0 150 | [0.1.0]: https://github.com/tweedegolf/stm32wlxx-hal 151 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "*-bsp", 5 | "examples", 6 | "hal", 7 | "testsuite", 8 | ] 9 | default-members = ["hal"] 10 | 11 | [profile.dev] 12 | codegen-units = 1 13 | debug = 2 14 | debug-assertions = true 15 | incremental = true 16 | lto = false 17 | opt-level = 3 18 | overflow-checks = true 19 | 20 | [profile.test] 21 | codegen-units = 1 22 | debug = 2 23 | debug-assertions = true 24 | incremental = true 25 | lto = false 26 | opt-level = 3 27 | overflow-checks = true 28 | 29 | [profile.bench] 30 | codegen-units = 1 31 | debug = 2 32 | debug-assertions = false 33 | incremental = false 34 | lto = false 35 | opt-level = 3 36 | overflow-checks = false 37 | 38 | [profile.release] 39 | codegen-units = 1 40 | debug = 2 41 | debug-assertions = false 42 | incremental = false 43 | lto = false 44 | opt-level = 3 45 | overflow-checks = false 46 | 47 | [workspace.package] 48 | edition = "2024" 49 | authors = ["Alex Martens "] 50 | repository = "https://github.com/stm32-rs/stm32wlxx-hal" 51 | license = "MIT OR Apache-2.0" 52 | # To update version change: 53 | # * BSP versions 54 | # * BSP HAL dependency versions 55 | # * README 56 | # * BSP READMES 57 | # * lock file 58 | # * CHANGELOG Unreleased (URL and header) 59 | version = "0.6.1" 60 | rust-version = "1.85" # update MSRV in CI, and shield in README 61 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present Alex Martens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stm32wlxx-hal 2 | 3 | [![CI](https://github.com/stm32-rs/stm32wlxx-hal/workflows/CI/badge.svg)](https://github.com/stm32-rs/stm32wlxx-hal/actions?query=branch%3Amain) 4 | [![stable-docs](https://img.shields.io/badge/docs-stable-blue)](https://docs.rs/stm32wlxx-hal/) 5 | [![nightly-docs](https://img.shields.io/badge/docs-nightly-black)](https://stm32-rs.github.io/stm32wlxx-hal/stm32wlxx_hal/index.html) 6 | [![crates.io](https://img.shields.io/crates/v/stm32wlxx-hal.svg)](https://crates.io/crates/stm32wlxx-hal) 7 | [![rustc](https://img.shields.io/badge/rustc-1.85+-blue.svg)](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) 8 | 9 | Embedded rust HAL (hardware abstraction layer) for the STM32WL series. 10 | 11 | This is still in development, the code that exists today covers basic usage of: 12 | 13 | * SubGHz LoRa TX + RX 14 | * SubGHz (G)FSK TX + RX 15 | * SPI 16 | * GPIO 17 | * UART 18 | * I2C 19 | * Low-power timers 20 | * ADC 21 | * DAC 22 | * PKA ECDSA signing + verification 23 | * Secure random number generation 24 | * AES ECB encryption + decryption 25 | * RTC date and time 26 | 27 | ## Usage 28 | 29 | ```toml 30 | [dependencies.stm32wlxx-hal] 31 | version = "0.6.1" 32 | features = [ 33 | # use exactly one to match your target hardware 34 | "stm32wl5x_cm0p", 35 | "stm32wl5x_cm4", 36 | "stm32wle5", 37 | # optional: use the cortex-m-rt interrupt interface 38 | "rt", 39 | # optional: use defmt 40 | "defmt", 41 | # optional: enable conversions with embedded-time types 42 | "embedded-time", 43 | # optional: use the real time clock (RTC) 44 | "chrono", 45 | ] 46 | ``` 47 | 48 | ## Examples 49 | 50 | All examples run on the NUCLEO-WL55JC2. 51 | 52 | Most examples are written in the form of on-target tests. See [testsuite/README.md](https://github.com/stm32-rs/stm32wlxx-hal/blob/main/testsuite/README.md) for details. 53 | 54 | There are also simple examples located in the `examples` crate. These can be run with the `run-ex` cargo alias: 55 | 56 | ```bash 57 | cargo run-ex gpio-blink 58 | ``` 59 | 60 | ### System Level Example 61 | 62 | The on-target tests and examples are a good starting point, but they demonstrate features independent of each-other. A system-level example using multiple features simultaneously is provided in a separate repo: [stm32wl-lightswitch-demo](https://github.com/newAM/stm32wl-lightswitch-demo) 63 | 64 | ## Unit Tests 65 | 66 | Off-target unit tests use the built-in cargo framework. You must specify the target device as a feature. 67 | 68 | ```bash 69 | cargo test --features stm32wl5x_cm4 70 | ``` 71 | 72 | ## Reference Documentation 73 | 74 | * [stm32wl5x reference manual](https://www.st.com/resource/en/reference_manual/rm0453-stm32wl5x-advanced-armbased-32bit-mcus-with-subghz-radio-solution-stmicroelectronics.pdf) 75 | * [stm32wlex reference manual](https://www.st.com/resource/en/reference_manual/rm0461-stm32wlex-advanced-armbased-32bit-mcus-with-subghz-radio-solution-stmicroelectronics.pdf) 76 | * [stm32wl55cc datasheet](https://www.st.com/resource/en/datasheet/stm32wl55cc.pdf) 77 | * [stm32wle5c8 datasheet](https://www.st.com/resource/en/datasheet/stm32wle5c8.pdf) 78 | * [stm32wl55xx stm32wl54xx erratum](https://www.st.com/resource/en/errata_sheet/es0500-stm32wl55xx-stm32wl54xx-device-errata-stmicroelectronics.pdf) 79 | * [stm32wle5xx stm32wle4xx erratum](https://www.st.com/resource/en/errata_sheet/es0506-stm32wle5xx-stm32wle4xx-device-errata-stmicroelectronics.pdf) 80 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | publish = false 4 | 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | rust-version.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | cortex-m = { version = "0.7", features = ["critical-section-single-core"] } 14 | cortex-m-rt = "0.7" 15 | defmt = "1" 16 | defmt-rtt = "1" 17 | panic-probe = { version = "1", features = ["print-defmt" ] } 18 | stm32wlxx-hal = { path = "../hal", features = ["stm32wl5x_cm4", "rt", "defmt"] } 19 | -------------------------------------------------------------------------------- /examples/examples/buzzer.rs: -------------------------------------------------------------------------------- 1 | // Sounds a buzzer, will work only for TTN Generic node with STM32WL5x 2 | // see https://github.com/TheThingsIndustries/generic-node-se 3 | // The datasheet of the buzzer `MLT-7525` shows that the oscillation frequency is `2700Hz` so the period 4 | // is around 185us 5 | 6 | #![no_std] 7 | #![no_main] 8 | 9 | use defmt_rtt as _; // global logger 10 | use panic_probe as _; // panic handler 11 | 12 | use stm32wlxx_hal::{ 13 | self as hal, 14 | cortex_m::{self, delay::Delay}, 15 | gpio::{Output, PinState, PortA, pins}, 16 | pac, 17 | util::new_delay, 18 | }; 19 | 20 | #[hal::cortex_m_rt::entry] 21 | fn main() -> ! { 22 | let mut dp: pac::Peripherals = defmt::unwrap!(pac::Peripherals::take()); 23 | let cp: pac::CorePeripherals = defmt::unwrap!(pac::CorePeripherals::take()); 24 | 25 | let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 26 | let mut buzzer: Output = 27 | cortex_m::interrupt::free(|cs| Output::default(gpioa.a15, cs)); 28 | 29 | let mut delay: Delay = new_delay(cp.SYST, &dp.RCC); 30 | 31 | defmt::info!("Starting buzzer"); 32 | 33 | loop { 34 | for &level in &[PinState::High, PinState::Low] { 35 | buzzer.set_level(level); 36 | delay.delay_us(185); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/examples/clock.rs: -------------------------------------------------------------------------------- 1 | // Prints default clock speeds, should work for all STM32WL5x boards. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use defmt_rtt as _; // global logger 7 | use panic_probe as _; // panic handler 8 | use stm32wlxx_hal::{self as hal, pac, rcc}; 9 | 10 | #[hal::cortex_m_rt::entry] 11 | fn main() -> ! { 12 | let dp: pac::Peripherals = defmt::unwrap!(pac::Peripherals::take()); 13 | 14 | defmt::println!("sysclk_hz: {}", rcc::sysclk_hz(&dp.RCC)); 15 | defmt::println!("hclk1_hz: {}", rcc::hclk1_hz(&dp.RCC)); 16 | defmt::println!("hclk2_hz: {}", rcc::hclk2_hz(&dp.RCC)); 17 | defmt::println!("hclk3_hz: {}", rcc::hclk3_hz(&dp.RCC)); 18 | defmt::println!("lsi_hz: {}", rcc::lsi_hz(&dp.RCC)); 19 | 20 | loop { 21 | hal::cortex_m::asm::bkpt(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/examples/gpio-blink.rs: -------------------------------------------------------------------------------- 1 | // Blinks the 3 LEDs on the NUCLEO-WL55JC2 in a sequence. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use defmt_rtt as _; // global logger 7 | use panic_probe as _; // panic handler 8 | use stm32wlxx_hal::{ 9 | self as hal, 10 | cortex_m::{self, delay::Delay}, 11 | gpio::{Output, PinState, PortB, pins}, 12 | pac, 13 | util::new_delay, 14 | }; 15 | 16 | #[hal::cortex_m_rt::entry] 17 | fn main() -> ! { 18 | let mut dp: pac::Peripherals = defmt::unwrap!(pac::Peripherals::take()); 19 | let cp: pac::CorePeripherals = defmt::unwrap!(pac::CorePeripherals::take()); 20 | 21 | let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 22 | let (mut led1, mut led2, mut led3): (Output, Output, Output) = 23 | cortex_m::interrupt::free(|cs| { 24 | ( 25 | Output::default(gpiob.b9, cs), 26 | Output::default(gpiob.b15, cs), 27 | Output::default(gpiob.b11, cs), 28 | ) 29 | }); 30 | 31 | let mut delay: Delay = new_delay(cp.SYST, &dp.RCC); 32 | 33 | defmt::info!("Starting blinky"); 34 | 35 | loop { 36 | for &level in &[PinState::High, PinState::Low] { 37 | led1.set_level(level); 38 | delay.delay_ms(600); 39 | led2.set_level(level); 40 | delay.delay_ms(600); 41 | led3.set_level(level); 42 | delay.delay_ms(600); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/examples/gpio-button-irq.rs: -------------------------------------------------------------------------------- 1 | // Prints over RTT when the state of B3 on the NUCLEO-WL55JC2 changes using interrupts. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use defmt_rtt as _; // global logger 7 | use panic_probe as _; // panic handler 8 | use stm32wlxx_hal::{ 9 | self as hal, cortex_m, 10 | gpio::{Exti, ExtiTrg, Input, PortC, Pull, pins}, 11 | pac::{self, interrupt}, 12 | }; 13 | 14 | #[hal::cortex_m_rt::entry] 15 | fn main() -> ! { 16 | let mut dp: pac::Peripherals = defmt::unwrap!(pac::Peripherals::take()); 17 | 18 | let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 19 | let _c6: Input = cortex_m::interrupt::free(|cs| Input::new(gpioc.c6, Pull::Up, cs)); 20 | defmt::assert!(!pins::C6::is_pending()); 21 | 22 | pins::C6::setup_exti_c1(&mut dp.EXTI, &mut dp.SYSCFG, ExtiTrg::Both); 23 | unsafe { pins::C6::unmask() }; 24 | 25 | loop { 26 | hal::cortex_m::asm::wfe(); 27 | } 28 | } 29 | 30 | #[interrupt] 31 | #[allow(non_snake_case)] 32 | fn EXTI9_5() { 33 | defmt::info!("B3 pressed or released!"); 34 | defmt::assert!(pins::C6::is_pending()); 35 | pins::C6::clear_exti(); 36 | } 37 | -------------------------------------------------------------------------------- /examples/examples/gpio-button.rs: -------------------------------------------------------------------------------- 1 | // Prints over RTT when the state of B3 on the NUCLEO-WL55JC2 changes. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use defmt_rtt as _; // global logger 7 | use panic_probe as _; // panic handler 8 | use stm32wlxx_hal::{ 9 | self as hal, cortex_m, 10 | gpio::{Input, PinState, PortC, Pull, pins}, 11 | pac, 12 | }; 13 | 14 | const fn pinstate_str(state: PinState) -> &'static str { 15 | match state { 16 | PinState::Low => "Low", 17 | PinState::High => "High", 18 | } 19 | } 20 | 21 | #[hal::cortex_m_rt::entry] 22 | fn main() -> ! { 23 | let mut dp: pac::Peripherals = defmt::unwrap!(pac::Peripherals::take()); 24 | 25 | let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 26 | let c6: Input = cortex_m::interrupt::free(|cs| Input::new(gpioc.c6, Pull::Up, cs)); 27 | 28 | let mut prev_level: PinState = c6.level(); 29 | defmt::info!("B3 initial level: {}", pinstate_str(prev_level)); 30 | 31 | loop { 32 | let level: PinState = c6.level(); 33 | if level != prev_level { 34 | defmt::info!( 35 | "B3 state changed from {} to {}", 36 | pinstate_str(prev_level), 37 | pinstate_str(level) 38 | ); 39 | prev_level = level; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/examples/info.rs: -------------------------------------------------------------------------------- 1 | // Prints device information, should work for all STM32WL5x boards. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use defmt_rtt as _; // global logger 7 | use panic_probe as _; // panic handler 8 | use stm32wlxx_hal::{ 9 | self as hal, 10 | info::{self, Package, Uid, Uid64}, 11 | }; 12 | 13 | #[hal::cortex_m_rt::entry] 14 | fn main() -> ! { 15 | defmt::println!("Flash size: {} KiB", info::flash_size_kibibyte()); 16 | defmt::println!("Package: {:?}", Package::from_device()); 17 | defmt::println!("UID64: {}", Uid64::from_device()); 18 | defmt::println!("UID: {}", Uid::from_device()); 19 | 20 | loop { 21 | hal::cortex_m::asm::bkpt(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | -------------------------------------------------------------------------------- /hal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stm32wlxx-hal" 3 | description = "Hardware abstraction layer for the STM32WL series microcontrollers." 4 | readme = "../README.md" 5 | keywords = ["arm", "cortex-m", "stm32", "hal"] 6 | categories = ["embedded", "hardware-support", "no-std"] 7 | 8 | authors.workspace = true 9 | edition.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | version.workspace = true 14 | 15 | [features] 16 | stm32wl5x_cm0p = ["stm32wl/stm32wl5x_cm0p"] 17 | stm32wl5x_cm4 = ["stm32wl/stm32wl5x_cm4"] 18 | stm32wle5 = ["stm32wl/stm32wle5"] 19 | rt = ["stm32wl/rt", "cortex-m-rt"] 20 | 21 | [dependencies] 22 | cfg-if = "1" 23 | chrono = { version = "0.4.23", default-features = false, optional = true } 24 | cortex-m = "0.7.3" 25 | cortex-m-rt = { version = "0.7", optional = true } 26 | defmt = { version = "1", optional = true } 27 | embedded-hal = { version = "0.2.6", features = ["unproven"] } 28 | embedded-time = { version = "0.12", optional = true } 29 | nb = "1" 30 | num-traits = { version = "0.2", default-features = false } 31 | num-integer = { version = "0.1", default-features = false } 32 | paste = "1" 33 | rand_core = "0.9" 34 | stm32wl = { version = "0.15.1", default-features = false } 35 | void = { version = "1", default-features = false } 36 | 37 | [dev-dependencies] 38 | static_assertions = "1" 39 | 40 | [package.metadata.docs.rs] 41 | all-features = false 42 | features = ["stm32wl5x_cm4", "rt", "embedded-time", "chrono"] 43 | rustdoc-args = ["--cfg", "docsrs"] 44 | -------------------------------------------------------------------------------- /hal/src/fmt.rs: -------------------------------------------------------------------------------- 1 | // Adapted from embassy-rs 2 | // https://github.com/embassy-rs/embassy/blob/2685dbfcf4446704cdd18afa7c83247c5fae36cb/embassy/src/fmt.rs 3 | // 4 | // This enables the optional use of defmt 5 | #![macro_use] 6 | #![allow(unused_macros, unknown_lints, unused_macro_rules)] 7 | 8 | macro_rules! assert { 9 | ($($x:tt)*) => { 10 | { 11 | #[cfg(not(feature = "defmt"))] 12 | ::core::assert!($($x)*); 13 | #[cfg(feature = "defmt")] 14 | ::defmt::assert!($($x)*); 15 | } 16 | }; 17 | } 18 | 19 | macro_rules! assert_eq { 20 | ($($x:tt)*) => { 21 | { 22 | #[cfg(not(feature = "defmt"))] 23 | ::core::assert_eq!($($x)*); 24 | #[cfg(feature = "defmt")] 25 | ::defmt::assert_eq!($($x)*); 26 | } 27 | }; 28 | } 29 | 30 | macro_rules! assert_ne { 31 | ($($x:tt)*) => { 32 | { 33 | #[cfg(not(feature = "defmt"))] 34 | ::core::assert_ne!($($x)*); 35 | #[cfg(feature = "defmt")] 36 | ::defmt::assert_ne!($($x)*); 37 | } 38 | }; 39 | } 40 | 41 | macro_rules! debug_assert { 42 | ($($x:tt)*) => { 43 | { 44 | #[cfg(not(feature = "defmt"))] 45 | ::core::debug_assert!($($x)*); 46 | #[cfg(feature = "defmt")] 47 | ::defmt::debug_assert!($($x)*); 48 | } 49 | }; 50 | } 51 | 52 | macro_rules! debug_assert_eq { 53 | ($($x:tt)*) => { 54 | { 55 | #[cfg(not(feature = "defmt"))] 56 | ::core::debug_assert_eq!($($x)*); 57 | #[cfg(feature = "defmt")] 58 | ::defmt::debug_assert_eq!($($x)*); 59 | } 60 | }; 61 | } 62 | 63 | macro_rules! debug_assert_ne { 64 | ($($x:tt)*) => { 65 | { 66 | #[cfg(not(feature = "defmt"))] 67 | ::core::debug_assert_ne!($($x)*); 68 | #[cfg(feature = "defmt")] 69 | ::defmt::debug_assert_ne!($($x)*); 70 | } 71 | }; 72 | } 73 | 74 | macro_rules! todo { 75 | ($($x:tt)*) => { 76 | { 77 | #[cfg(not(feature = "defmt"))] 78 | ::core::todo!($($x)*); 79 | #[cfg(feature = "defmt")] 80 | ::defmt::todo!($($x)*); 81 | } 82 | }; 83 | } 84 | 85 | macro_rules! unreachable { 86 | ($($x:tt)*) => { 87 | { 88 | #[cfg(not(feature = "defmt"))] 89 | ::core::unreachable!($($x)*); 90 | #[cfg(feature = "defmt")] 91 | ::defmt::unreachable!($($x)*); 92 | } 93 | }; 94 | } 95 | 96 | macro_rules! panic { 97 | ($($x:tt)*) => { 98 | { 99 | #[cfg(not(feature = "defmt"))] 100 | ::core::panic!($($x)*); 101 | #[cfg(feature = "defmt")] 102 | ::defmt::panic!($($x)*); 103 | } 104 | }; 105 | } 106 | 107 | macro_rules! trace { 108 | ($s:literal $(, $x:expr)* $(,)?) => { 109 | { 110 | #[cfg(feature = "defmt")] 111 | ::defmt::trace!($s $(, $x)*); 112 | #[cfg(not(feature="defmt"))] 113 | let _ = ($( & $x ),*); 114 | } 115 | }; 116 | } 117 | 118 | macro_rules! debug { 119 | ($s:literal $(, $x:expr)* $(,)?) => { 120 | { 121 | #[cfg(feature = "defmt")] 122 | ::defmt::debug!($s $(, $x)*); 123 | #[cfg(not(feature="defmt"))] 124 | let _ = ($( & $x ),*); 125 | } 126 | }; 127 | } 128 | 129 | macro_rules! info { 130 | ($s:literal $(, $x:expr)* $(,)?) => { 131 | { 132 | #[cfg(feature = "defmt")] 133 | ::defmt::info!($s $(, $x)*); 134 | #[cfg(not(feature="defmt"))] 135 | let _ = ($( & $x ),*); 136 | } 137 | }; 138 | } 139 | 140 | macro_rules! warn { 141 | ($s:literal $(, $x:expr)* $(,)?) => { 142 | { 143 | #[cfg(feature = "defmt")] 144 | ::defmt::warn!($s $(, $x)*); 145 | #[cfg(not(feature="defmt"))] 146 | let _ = ($( & $x ),*); 147 | } 148 | }; 149 | } 150 | 151 | macro_rules! error { 152 | ($s:literal $(, $x:expr)* $(,)?) => { 153 | { 154 | #[cfg(feature = "defmt")] 155 | ::defmt::error!($s $(, $x)*); 156 | #[cfg(not(feature="defmt"))] 157 | let _ = ($( & $x ),*); 158 | } 159 | }; 160 | } 161 | 162 | #[cfg(feature = "defmt")] 163 | macro_rules! unwrap { 164 | ($($x:tt)*) => { 165 | ::defmt::unwrap!($($x)*) 166 | }; 167 | } 168 | 169 | #[cfg(not(feature = "defmt"))] 170 | macro_rules! unwrap { 171 | ($arg:expr) => { 172 | match $crate::fmt::Try::into_result($arg) { 173 | ::core::result::Result::Ok(t) => t, 174 | ::core::result::Result::Err(e) => { 175 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); 176 | } 177 | } 178 | }; 179 | ($arg:expr, $($msg:expr),+ $(,)? ) => { 180 | match $crate::fmt::Try::into_result($arg) { 181 | ::core::result::Result::Ok(t) => t, 182 | ::core::result::Result::Err(e) => { 183 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); 184 | } 185 | } 186 | } 187 | } 188 | 189 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 190 | pub struct NoneError; 191 | 192 | pub trait Try { 193 | type Ok; 194 | type Error; 195 | #[allow(dead_code)] 196 | fn into_result(self) -> Result; 197 | } 198 | 199 | impl Try for Option { 200 | type Ok = T; 201 | type Error = NoneError; 202 | 203 | #[inline] 204 | fn into_result(self) -> Result { 205 | self.ok_or(NoneError) 206 | } 207 | } 208 | 209 | impl Try for Result { 210 | type Ok = T; 211 | type Error = E; 212 | 213 | #[inline] 214 | fn into_result(self) -> Self { 215 | self 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /hal/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! STM32WL Hardware Abstraction Layer. 2 | //! 3 | //! This documentation was generated for the 4 | #![cfg_attr(feature = "stm32wl5x_cm0p", doc = "STM32WL5X (CM0+ core).")] 5 | #![cfg_attr(feature = "stm32wl5x_cm4", doc = "STM32WL5X (CM4 core).")] 6 | #![cfg_attr(feature = "stm32wle5", doc = "STM32WLE5.")] 7 | //! 8 | #![cfg_attr(not(test), no_std)] 9 | #![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] 10 | #![warn(missing_docs)] 11 | #![warn(clippy::wildcard_imports)] 12 | 13 | #[cfg(any( 14 | all(feature = "stm32wl5x_cm0p", feature = "stm32wl5x_cm4"), 15 | all(feature = "stm32wl5x_cm0p", feature = "stm32wle5"), 16 | all(feature = "stm32wl5x_cm4", feature = "stm32wle5"), 17 | ))] 18 | compile_error!( 19 | "Multiple chip features activated. \ 20 | You must activate exactly one of the following features: \ 21 | stm32wl5x_cm0p, stm32wl5x_cm4, stm32wle5" 22 | ); 23 | 24 | // cfg_hide is not yet working correctly for re-exports 25 | // https://github.com/rust-lang/rust/issues/85043 26 | // https://github.com/rust-lang/rust/issues/88743 27 | cfg_if::cfg_if! { 28 | if #[cfg(feature = "stm32wl5x_cm0p")] { 29 | /// Peripheral access crate 30 | // #[cfg_attr(docsrs, doc(cfg_hide(feature = "stm32wl5x_cm0p")))] 31 | pub use stm32wl::stm32wl5x_cm0p as pac; 32 | } else if #[cfg(feature = "stm32wl5x_cm4")] { 33 | /// Peripheral access crate 34 | // #[cfg_attr(docsrs, doc(cfg_hide(feature = "stm32wl5x_cm4")))] 35 | pub use stm32wl::stm32wl5x_cm4 as pac; 36 | } else if #[cfg(feature = "stm32wle5")] { 37 | /// Peripheral access crate 38 | // #[cfg_attr(docsrs, doc(cfg_hide(feature = "stm32wle5")))] 39 | pub use stm32wl::stm32wle5 as pac; 40 | } else { 41 | core::compile_error!("You must select your hardware with a feature flag"); 42 | } 43 | } 44 | 45 | // This mod MUST go first, so that the others see its macros. 46 | pub(crate) mod fmt; 47 | pub(crate) mod macros; 48 | 49 | pub mod adc; 50 | pub mod aes; 51 | pub mod dac; 52 | pub mod dma; 53 | pub mod flash; 54 | pub mod gpio; 55 | pub mod i2c; 56 | pub mod info; 57 | pub mod lptim; 58 | pub mod pka; 59 | pub mod pwr; 60 | pub mod rcc; 61 | pub mod rng; 62 | #[cfg(feature = "chrono")] 63 | pub mod rtc; 64 | pub mod spi; 65 | pub mod subghz; 66 | pub mod uart; 67 | pub mod util; 68 | 69 | mod ratio; 70 | pub use ratio::Ratio; 71 | 72 | #[cfg(feature = "rt")] 73 | pub use cortex_m_rt; 74 | 75 | #[cfg(feature = "chrono")] 76 | pub use chrono; 77 | pub use cortex_m; 78 | pub use embedded_hal; 79 | 80 | #[cfg(feature = "embedded-time")] 81 | pub use embedded_time; 82 | -------------------------------------------------------------------------------- /hal/src/lptim/cr.rs: -------------------------------------------------------------------------------- 1 | /// Control register. 2 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 3 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 4 | pub struct Cr { 5 | val: u32, 6 | } 7 | 8 | impl Cr { 9 | /// Reset value of the register. 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use stm32wlxx_hal::lptim::Cr; 15 | /// assert_eq!(Cr::RESET.raw(), 0); 16 | /// ``` 17 | pub const RESET: Cr = Cr::new(0); 18 | 19 | /// Reset value + timer disabled. 20 | /// 21 | /// This is equivalent to the reset value, it is provided to make the code 22 | /// more expressive. 23 | /// 24 | /// # Example 25 | /// 26 | /// ``` 27 | /// use stm32wlxx_hal::lptim::Cr; 28 | /// assert_eq!(Cr::DISABLE.enabled(), false); 29 | /// assert_eq!(Cr::DISABLE, Cr::RESET); 30 | /// ``` 31 | pub const DISABLE: Cr = Cr::RESET.set_enable(false); 32 | 33 | /// Create a new Cr register from a raw value. 34 | /// 35 | /// # Example 36 | /// 37 | /// ``` 38 | /// use stm32wlxx_hal::lptim::Cr; 39 | /// const CR: Cr = Cr::new(0b1); 40 | /// ``` 41 | pub const fn new(val: u32) -> Cr { 42 | Cr { val } 43 | } 44 | 45 | /// Get the raw value of the register. 46 | /// 47 | /// # Example 48 | /// 49 | /// ``` 50 | /// use stm32wlxx_hal::lptim::Cr; 51 | /// const CR: Cr = Cr::new(0x1234_5678); 52 | /// assert_eq!(CR.raw(), 0x1234_5678); 53 | /// ``` 54 | pub const fn raw(self) -> u32 { 55 | self.val 56 | } 57 | 58 | /// Set the counter reset. 59 | /// 60 | /// This bit is cleared by hardware. 61 | #[must_use = "set_cnt_rst returns a modified Cr"] 62 | pub const fn set_cnt_rst(mut self) -> Cr { 63 | self.val |= 1 << 3; 64 | self 65 | } 66 | 67 | /// Returns `true` if the counter reset bit is set. 68 | pub const fn cnt_rst(&self) -> bool { 69 | self.val & (1 << 3) != 0 70 | } 71 | 72 | /// Start the timer in continuous mode. 73 | #[must_use = "set_continuous returns a modified Cr"] 74 | pub const fn set_continuous(mut self) -> Cr { 75 | self.val |= 1 << 2; 76 | self 77 | } 78 | 79 | /// Start the timer in single-shot mode. 80 | #[must_use = "set_single returns a modified Cr"] 81 | pub const fn set_single(mut self) -> Cr { 82 | self.val |= 1 << 1; 83 | self 84 | } 85 | 86 | /// Set the enable bit for the timer. 87 | /// 88 | /// # Example 89 | /// 90 | /// ``` 91 | /// use stm32wlxx_hal::lptim::Cr; 92 | /// 93 | /// let cr = Cr::RESET; 94 | /// assert_eq!(cr.enabled(), false); 95 | /// 96 | /// let cr = cr.set_enable(true); 97 | /// assert_eq!(cr.enabled(), true); 98 | /// 99 | /// let cr = cr.set_enable(false); 100 | /// assert_eq!(cr.enabled(), false); 101 | /// ``` 102 | #[must_use = "set_enable returns a modified Cr"] 103 | pub const fn set_enable(self, en: bool) -> Cr { 104 | if en { self.enable() } else { self.disable() } 105 | } 106 | 107 | /// Enable the LPTIM peripheral. 108 | /// 109 | /// # Example 110 | /// 111 | /// ``` 112 | /// use stm32wlxx_hal::lptim::Cr; 113 | /// 114 | /// let cr = Cr::RESET; 115 | /// assert_eq!(cr.enabled(), false); 116 | /// 117 | /// let cr = cr.enable(); 118 | /// assert_eq!(cr.enabled(), true); 119 | /// 120 | /// let cr = cr.disable(); 121 | /// assert_eq!(cr.enabled(), false); 122 | /// ``` 123 | #[must_use = "enable returns a modified Cr"] 124 | pub const fn enable(mut self) -> Cr { 125 | self.val |= 0b1; 126 | self 127 | } 128 | 129 | /// Disable the LPTIM peripheral. 130 | /// 131 | /// # Example 132 | /// 133 | /// ``` 134 | /// use stm32wlxx_hal::lptim::Cr; 135 | /// 136 | /// let cr = Cr::RESET; 137 | /// assert_eq!(cr.enabled(), false); 138 | /// 139 | /// let cr = cr.enable(); 140 | /// assert_eq!(cr.enabled(), true); 141 | /// 142 | /// let cr = cr.disable(); 143 | /// assert_eq!(cr.enabled(), false); 144 | /// ``` 145 | #[must_use = "disable returns a modified Cr"] 146 | pub const fn disable(mut self) -> Cr { 147 | self.val &= !0b1; 148 | self 149 | } 150 | 151 | /// Returns `true` if the timer is enabled. 152 | pub const fn enabled(&self) -> bool { 153 | self.val & 0b1 != 0 154 | } 155 | } 156 | 157 | impl From for Cr { 158 | fn from(val: u32) -> Self { 159 | Self { val } 160 | } 161 | } 162 | 163 | impl From for u32 { 164 | fn from(cr: Cr) -> Self { 165 | cr.val 166 | } 167 | } 168 | 169 | impl Default for Cr { 170 | fn default() -> Self { 171 | Cr::RESET 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /hal/src/macros.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | #![allow(unknown_lints, unused_macro_rules)] 3 | 4 | macro_rules! typestate { 5 | ($name:ident, $doc:expr) => { 6 | paste::paste! { 7 | #[doc = "[Typestate] for " $doc "."] 8 | /// 9 | /// [Typestate]: https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html 10 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 11 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 12 | pub struct $name { 13 | _priv: (), 14 | } 15 | } 16 | 17 | impl $name { 18 | #[allow(dead_code)] 19 | pub(crate) const fn new() -> Self { 20 | Self { _priv: () } 21 | } 22 | } 23 | }; 24 | ($name:ident) => { 25 | paste::paste! { 26 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 27 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 28 | struct $name { 29 | _priv: (), 30 | } 31 | } 32 | 33 | impl $name { 34 | #[allow(dead_code)] 35 | pub(crate) const fn new() -> Self { 36 | Self { _priv: () } 37 | } 38 | } 39 | }; 40 | } 41 | 42 | // helper for conditional compilation 43 | #[cfg(any(feature = "stm32wl5x_cm4", feature = "stm32wle5"))] 44 | macro_rules! c1_c2 { 45 | ($c1:expr, $c2:expr) => { 46 | $c1 47 | }; 48 | ($c1:expr, $c2:expr,) => { 49 | $c1 50 | }; 51 | } 52 | 53 | #[cfg(feature = "stm32wl5x_cm0p")] 54 | macro_rules! c1_c2 { 55 | ($c1:expr, $c2:expr) => { 56 | $c2 57 | }; 58 | ($c1:expr, $c2:expr,) => { 59 | $c2 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /hal/src/pwr.rs: -------------------------------------------------------------------------------- 1 | //! Power control 2 | 3 | use core::sync::atomic::{Ordering::SeqCst, compiler_fence}; 4 | 5 | use cortex_m::interrupt::CriticalSection; 6 | 7 | use crate::{pac, rcc::MsiRange}; 8 | 9 | const SCB_SCR_SLEEPDEEP: u32 = 0x1 << 2; 10 | const SCB_SCR_SLEEPONEXIT: u32 = 0x1 << 1; 11 | 12 | /// Wakeup pin options for [`setup_wakeup_pins`]. 13 | #[derive(Debug)] 14 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 15 | pub enum WakeupPin { 16 | /// Wakeup pin disabled. 17 | Disabled, 18 | /// Wakeup pin enabled with a rising edge. 19 | Rising, 20 | /// Wakeup pin enabled with a falling edge. 21 | Falling, 22 | } 23 | 24 | impl WakeupPin { 25 | const fn en(&self) -> bool { 26 | !matches!(self, WakeupPin::Disabled) 27 | } 28 | 29 | const fn edge(&self) -> bool { 30 | matches!(self, WakeupPin::Falling) 31 | } 32 | } 33 | 34 | /// Setup the wakeup pins for shutdown and standby low-power modes. 35 | /// 36 | /// The reference manual is not 100% specific which wakeup pin corresponds to 37 | /// which physical pin, a footnote of 38 | /// "Table 45. Functionalities depending on system operating mode" 39 | /// in RM0453 Rev 2 implies the following: 40 | /// 41 | /// * WP1 corresponds to [`A0`](crate::gpio::pins::A0) 42 | /// * WP2 corresponds to [`C13`](crate::gpio::pins::C13) 43 | /// * WP3 corresponds to [`B3`](crate::gpio::pins::B3) 44 | /// 45 | /// If you know where to find more concrete information on this please open 46 | /// an issue. 47 | /// 48 | /// # Example 49 | /// 50 | /// Enable A0 to wakeup on a falling edge. 51 | /// 52 | /// ```no_run 53 | /// use stm32wlxx_hal::{ 54 | /// pac, 55 | /// pwr::{WakeupPin, setup_wakeup_pins}, 56 | /// }; 57 | /// 58 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 59 | /// setup_wakeup_pins( 60 | /// &mut dp.PWR, 61 | /// WakeupPin::Falling, 62 | /// WakeupPin::Disabled, 63 | /// WakeupPin::Disabled, 64 | /// ); 65 | /// ``` 66 | #[inline] 67 | pub fn setup_wakeup_pins(pwr: &mut pac::PWR, wp1: WakeupPin, wp2: WakeupPin, wp3: WakeupPin) { 68 | pwr.cr3.modify(|_, w| { 69 | w.ewup1().bit(wp1.en()); 70 | w.ewup2().bit(wp2.en()); 71 | w.ewup3().bit(wp3.en()) 72 | }); 73 | pwr.cr4.modify(|_, w| { 74 | w.wp1().bit(wp1.edge()); 75 | w.wp2().bit(wp2.edge()); 76 | w.wp3().bit(wp3.edge()) 77 | }); 78 | } 79 | 80 | /// Enter shutdown mode immediately. 81 | /// 82 | /// Wakeup pins should be configured with [`setup_wakeup_pins`] unless 83 | /// you intend to wakeup only via reset. 84 | /// 85 | /// This will: 86 | /// 87 | /// 1. Disable interrupts. 88 | /// 2. Set `PWR.CR1.LPMS` to shutdown. 89 | /// 3. Set `SCB.SCR.SLEEPDEEP`. 90 | /// 4. Enter WFI. 91 | /// 92 | /// # Example 93 | /// 94 | /// ```no_run 95 | /// use stm32wlxx_hal::{pac, pwr::shutdown}; 96 | /// 97 | /// let mut cp: pac::CorePeripherals = pac::CorePeripherals::take().unwrap(); 98 | /// // SLEEPDEEP is implemented with retention 99 | /// // generally you will want to clear this at power-on when using shutdown 100 | /// cp.SCB.clear_sleepdeep(); 101 | /// 102 | /// // ... do things 103 | /// 104 | /// shutdown(); 105 | /// ``` 106 | #[inline] 107 | pub fn shutdown() -> ! { 108 | cortex_m::interrupt::disable(); 109 | 110 | // safety: interrupts are disabled and no way to use core 2 currently 111 | // provided by the HAL 112 | unsafe { (*pac::PWR::PTR).cr1.modify(|_, w| w.lpms().shutdown()) }; 113 | 114 | // safety: interrupts are disabled core 2 cannot access our core registers 115 | unsafe { (*pac::SCB::PTR).scr.modify(|scr| scr | SCB_SCR_SLEEPDEEP) }; 116 | 117 | cortex_m::asm::wfi(); 118 | 119 | // technically unreachable 120 | // the unreachable!() macro takes up needless code space 121 | loop { 122 | compiler_fence(SeqCst) 123 | } 124 | } 125 | 126 | /// Enable shutdown on return from ISR or the next WFI or WFE. 127 | /// 128 | /// # Example 129 | /// 130 | /// ```no_run 131 | /// use stm32wlxx_hal::{pac, pwr::enable_shutdown_sleeponexit}; 132 | /// 133 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 134 | /// let mut cp: pac::CorePeripherals = pac::CorePeripherals::take().unwrap(); 135 | /// // SLEEPDEEP is implemented with retention 136 | /// // generally you will want to clear this at power-on when using shutdown 137 | /// cp.SCB.clear_sleepdeep(); 138 | /// 139 | /// // ... do things 140 | /// 141 | /// enable_shutdown_sleeponexit(&mut dp.PWR, &mut cp.SCB); 142 | /// ``` 143 | #[inline] 144 | pub fn enable_shutdown_sleeponexit(pwr: &mut pac::PWR, scb: &mut pac::SCB) { 145 | unsafe { 146 | scb.scr 147 | .modify(|scr| scr | SCB_SCR_SLEEPDEEP | SCB_SCR_SLEEPONEXIT) 148 | }; 149 | pwr.cr1.modify(|_, w| w.lpms().shutdown()); 150 | } 151 | 152 | /// Enable shutdown on the next WFI or WFE. 153 | /// 154 | /// # Example 155 | /// 156 | /// ```no_run 157 | /// use stm32wlxx_hal::{pac, pwr::enable_shutdown}; 158 | /// 159 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 160 | /// let mut cp: pac::CorePeripherals = pac::CorePeripherals::take().unwrap(); 161 | /// // SLEEPDEEP is implemented with retention 162 | /// // generally you will want to clear this at power-on when using shutdown 163 | /// cp.SCB.clear_sleepdeep(); 164 | /// 165 | /// // ... do things 166 | /// 167 | /// enable_shutdown(&mut dp.PWR, &mut cp.SCB); 168 | /// ``` 169 | #[inline] 170 | pub fn enable_shutdown(pwr: &mut pac::PWR, scb: &mut pac::SCB) { 171 | unsafe { scb.scr.modify(|scr| scr | SCB_SCR_SLEEPDEEP) }; 172 | pwr.cr1.modify(|_, w| w.lpms().shutdown()); 173 | } 174 | 175 | /// MSI clock ranges for [`enter_lprun_msi`]. 176 | #[repr(u8)] 177 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 178 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 179 | pub enum LprunRange { 180 | /// Range 0 around 100 kHz. 181 | Range100k = 0b0000, 182 | /// Range 1 around 200 kHz. 183 | Range200k = 0b0001, 184 | /// Range 2 around 400 kHz. 185 | Range400k = 0b0010, 186 | /// Range 3 around 800 kHz. 187 | Range800k = 0b0011, 188 | /// Range 4 around 1 MHz. 189 | Range1M = 0b0100, 190 | } 191 | 192 | impl From for MsiRange { 193 | fn from(lprr: LprunRange) -> Self { 194 | match lprr { 195 | LprunRange::Range100k => MsiRange::Range100k, 196 | LprunRange::Range200k => MsiRange::Range200k, 197 | LprunRange::Range400k => MsiRange::Range400k, 198 | LprunRange::Range800k => MsiRange::Range800k, 199 | LprunRange::Range1M => MsiRange::Range1M, 200 | } 201 | } 202 | } 203 | 204 | /// Enter low-power run mode with MSI as a clock source. 205 | /// 206 | /// # Safety 207 | /// 208 | /// 1. This will disable the HSE32 clock if not already disabled. 209 | /// Before calling this method peripherals (e.g. SUBGHZ, RTC) should 210 | /// be switched to a different clock source. 211 | /// 212 | /// # Example 213 | /// 214 | /// ```no_run 215 | /// use stm32wlxx_hal::{ 216 | /// pac, 217 | /// pwr::{LprunRange, enter_lprun_msi}, 218 | /// }; 219 | /// 220 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 221 | /// cortex_m::interrupt::free(|cs| unsafe { 222 | /// enter_lprun_msi( 223 | /// &mut dp.FLASH, 224 | /// &mut dp.PWR, 225 | /// &mut dp.RCC, 226 | /// LprunRange::Range1M, 227 | /// cs, 228 | /// ) 229 | /// }); 230 | /// ``` 231 | #[inline] 232 | pub unsafe fn enter_lprun_msi( 233 | flash: &mut pac::FLASH, 234 | pwr: &mut pac::PWR, 235 | rcc: &mut pac::RCC, 236 | range: LprunRange, 237 | cs: &CriticalSection, 238 | ) { 239 | unsafe { 240 | crate::rcc::set_sysclk_msi(flash, pwr, rcc, range.into(), cs); 241 | rcc.cr.modify(|_, w| w.hseon().disabled()); 242 | pwr.cr1.modify(|_, w| w.lpr().low_power_mode()); 243 | } 244 | } 245 | 246 | /// Exit low-power run mode. 247 | /// 248 | /// This will not increase the clock frequencies after exit. 249 | /// 250 | /// # Example 251 | /// 252 | /// ```no_run 253 | /// use stm32wlxx_hal::{ 254 | /// pac, 255 | /// pwr::{LprunRange, enter_lprun_msi, exit_lprun}, 256 | /// }; 257 | /// 258 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 259 | /// cortex_m::interrupt::free(|cs| unsafe { 260 | /// enter_lprun_msi( 261 | /// &mut dp.FLASH, 262 | /// &mut dp.PWR, 263 | /// &mut dp.RCC, 264 | /// LprunRange::Range1M, 265 | /// cs, 266 | /// ) 267 | /// }); 268 | /// 269 | /// exit_lprun(&mut dp.PWR); 270 | /// ``` 271 | #[inline] 272 | pub fn exit_lprun(pwr: &mut pac::PWR) { 273 | pwr.cr1.modify(|_, w| w.lpr().main_mode()); 274 | while !pwr.sr2.read().reglpf().is_main() {} 275 | } 276 | -------------------------------------------------------------------------------- /hal/src/ratio.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Div, Mul}; 2 | use num_traits::{CheckedAdd, CheckedDiv, CheckedMul}; 3 | 4 | /// Represents the ratio between two numbers. 5 | #[derive(Copy, Clone, Debug)] 6 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 7 | pub struct Ratio { 8 | /// Numerator. 9 | numer: T, 10 | /// Denominator. 11 | denom: T, 12 | } 13 | 14 | impl Ratio { 15 | /// Creates a new `Ratio`. 16 | #[inline(always)] 17 | pub(crate) const fn new_raw(numer: T, denom: T) -> Ratio { 18 | Ratio { numer, denom } 19 | } 20 | 21 | /// Gets an immutable reference to the numerator. 22 | #[inline(always)] 23 | pub const fn numer(&self) -> &T { 24 | &self.numer 25 | } 26 | 27 | /// Gets an immutable reference to the denominator. 28 | #[inline(always)] 29 | pub const fn denom(&self) -> &T { 30 | &self.denom 31 | } 32 | } 33 | 34 | impl Ratio { 35 | /// Converts to an integer, rounding towards zero. 36 | #[inline(always)] 37 | pub fn to_integer(&self) -> T { 38 | unwrap!(self.numer().checked_div(self.denom())) 39 | } 40 | } 41 | 42 | impl Div for Ratio { 43 | type Output = Self; 44 | 45 | #[inline(always)] 46 | fn div(mut self, rhs: T) -> Self::Output { 47 | self.denom = unwrap!(self.denom().checked_mul(&rhs)); 48 | self 49 | } 50 | } 51 | 52 | impl Mul for Ratio { 53 | type Output = Self; 54 | 55 | #[inline(always)] 56 | fn mul(mut self, rhs: T) -> Self::Output { 57 | self.numer = unwrap!(self.numer().checked_mul(&rhs)); 58 | self 59 | } 60 | } 61 | 62 | impl Add for Ratio { 63 | type Output = Self; 64 | 65 | #[inline(always)] 66 | fn add(mut self, rhs: T) -> Self::Output { 67 | self.numer = unwrap!(unwrap!(self.denom().checked_mul(&rhs)).checked_add(self.numer())); 68 | self 69 | } 70 | } 71 | 72 | macro_rules! impl_from_for_float { 73 | ($from:ident) => { 74 | impl From> for f32 { 75 | #[inline(always)] 76 | fn from(r: Ratio<$from>) -> Self { 77 | (r.numer as f32) / (r.denom as f32) 78 | } 79 | } 80 | 81 | impl From> for f64 { 82 | #[inline(always)] 83 | fn from(r: Ratio<$from>) -> Self { 84 | (r.numer as f64) / (r.denom as f64) 85 | } 86 | } 87 | }; 88 | } 89 | 90 | impl_from_for_float!(u8); 91 | impl_from_for_float!(u16); 92 | impl_from_for_float!(u32); 93 | impl_from_for_float!(u64); 94 | impl_from_for_float!(u128); 95 | impl_from_for_float!(i8); 96 | impl_from_for_float!(i16); 97 | impl_from_for_float!(i32); 98 | impl_from_for_float!(i64); 99 | impl_from_for_float!(i128); 100 | 101 | impl core::fmt::Display for Ratio { 102 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 103 | core::write!(f, "{} / {}", self.numer(), self.denom()) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::Ratio; 110 | 111 | #[test] 112 | fn basics() { 113 | let mut r = Ratio::new_raw(1, 2) + 2; 114 | assert_eq!(*r.numer(), 5); 115 | assert_eq!(*r.denom(), 2); 116 | assert_eq!(r.to_integer(), 2); 117 | 118 | r = r * 2; 119 | assert_eq!(*r.numer(), 10); 120 | assert_eq!(*r.denom(), 2); 121 | assert_eq!(r.to_integer(), 5); 122 | 123 | r = r / 2; 124 | assert_eq!(*r.numer(), 10); 125 | assert_eq!(*r.denom(), 4); 126 | assert_eq!(r.to_integer(), 2); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /hal/src/subghz/bit_sync.rs: -------------------------------------------------------------------------------- 1 | /// Bit synchronization. 2 | /// 3 | /// This must be cleared to `0x00` (the reset value) when using packet types 4 | /// other than LoRa. 5 | /// 6 | /// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync). 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 9 | pub struct BitSync { 10 | val: u8, 11 | } 12 | 13 | impl BitSync { 14 | /// Bit synchronization register reset value. 15 | pub const RESET: BitSync = BitSync { val: 0x00 }; 16 | 17 | /// Create a new [`BitSync`] structure from a raw value. 18 | /// 19 | /// Reserved bits will be masked. 20 | pub const fn from_raw(raw: u8) -> Self { 21 | Self { val: raw & 0x70 } 22 | } 23 | 24 | /// Get the raw value of the [`BitSync`] register. 25 | pub const fn as_bits(&self) -> u8 { 26 | self.val 27 | } 28 | 29 | /// LoRa simple bit synchronization enable. 30 | /// 31 | /// # Example 32 | /// 33 | /// Enable simple bit synchronization. 34 | /// 35 | /// ``` 36 | /// use stm32wlxx_hal::subghz::BitSync; 37 | /// 38 | /// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true); 39 | /// # assert_eq!(u8::from(BIT_SYNC), 0x40u8); 40 | /// ``` 41 | #[must_use = "set_simple_bit_sync_en returns a modified BitSync"] 42 | pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync { 43 | if en { 44 | self.val |= 1 << 6; 45 | } else { 46 | self.val &= !(1 << 6); 47 | } 48 | self 49 | } 50 | 51 | /// Returns `true` if simple bit synchronization is enabled. 52 | /// 53 | /// # Example 54 | /// 55 | /// ``` 56 | /// use stm32wlxx_hal::subghz::BitSync; 57 | /// 58 | /// let bs: BitSync = BitSync::RESET; 59 | /// assert_eq!(bs.simple_bit_sync_en(), false); 60 | /// let bs: BitSync = bs.set_simple_bit_sync_en(true); 61 | /// assert_eq!(bs.simple_bit_sync_en(), true); 62 | /// let bs: BitSync = bs.set_simple_bit_sync_en(false); 63 | /// assert_eq!(bs.simple_bit_sync_en(), false); 64 | /// ``` 65 | pub const fn simple_bit_sync_en(&self) -> bool { 66 | self.val & (1 << 6) != 0 67 | } 68 | 69 | /// LoRa RX data inversion. 70 | /// 71 | /// # Example 72 | /// 73 | /// Invert receive data. 74 | /// 75 | /// ``` 76 | /// use stm32wlxx_hal::subghz::BitSync; 77 | /// 78 | /// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true); 79 | /// # assert_eq!(u8::from(BIT_SYNC), 0x20u8); 80 | /// ``` 81 | #[must_use = "set_rx_data_inv returns a modified BitSync"] 82 | pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync { 83 | if inv { 84 | self.val |= 1 << 5; 85 | } else { 86 | self.val &= !(1 << 5); 87 | } 88 | self 89 | } 90 | 91 | /// Returns `true` if LoRa RX data is inverted. 92 | /// 93 | /// # Example 94 | /// 95 | /// ``` 96 | /// use stm32wlxx_hal::subghz::BitSync; 97 | /// 98 | /// let bs: BitSync = BitSync::RESET; 99 | /// assert_eq!(bs.rx_data_inv(), false); 100 | /// let bs: BitSync = bs.set_rx_data_inv(true); 101 | /// assert_eq!(bs.rx_data_inv(), true); 102 | /// let bs: BitSync = bs.set_rx_data_inv(false); 103 | /// assert_eq!(bs.rx_data_inv(), false); 104 | /// ``` 105 | pub const fn rx_data_inv(&self) -> bool { 106 | self.val & (1 << 5) != 0 107 | } 108 | 109 | /// LoRa normal bit synchronization enable. 110 | /// 111 | /// # Example 112 | /// 113 | /// Enable normal bit synchronization. 114 | /// 115 | /// ``` 116 | /// use stm32wlxx_hal::subghz::BitSync; 117 | /// 118 | /// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true); 119 | /// # assert_eq!(u8::from(BIT_SYNC), 0x10u8); 120 | /// ``` 121 | #[must_use = "set_norm_bit_sync_en returns a modified BitSync"] 122 | pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync { 123 | if en { 124 | self.val |= 1 << 4; 125 | } else { 126 | self.val &= !(1 << 4); 127 | } 128 | self 129 | } 130 | 131 | /// Returns `true` if normal bit synchronization is enabled. 132 | /// 133 | /// # Example 134 | /// 135 | /// ``` 136 | /// use stm32wlxx_hal::subghz::BitSync; 137 | /// 138 | /// let bs: BitSync = BitSync::RESET; 139 | /// assert_eq!(bs.norm_bit_sync_en(), false); 140 | /// let bs: BitSync = bs.set_norm_bit_sync_en(true); 141 | /// assert_eq!(bs.norm_bit_sync_en(), true); 142 | /// let bs: BitSync = bs.set_norm_bit_sync_en(false); 143 | /// assert_eq!(bs.norm_bit_sync_en(), false); 144 | /// ``` 145 | pub const fn norm_bit_sync_en(&self) -> bool { 146 | self.val & (1 << 4) != 0 147 | } 148 | } 149 | 150 | impl From for u8 { 151 | fn from(bs: BitSync) -> Self { 152 | bs.val 153 | } 154 | } 155 | 156 | impl Default for BitSync { 157 | fn default() -> Self { 158 | Self::RESET 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /hal/src/subghz/cad_params.rs: -------------------------------------------------------------------------------- 1 | use super::Timeout; 2 | 3 | /// Number of symbols used for channel activity detection scans. 4 | /// 5 | /// Argument of [`CadParams::set_num_symbol`]. 6 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | #[repr(u8)] 9 | pub enum NbCadSymbol { 10 | /// 1 symbol. 11 | S1 = 0x0, 12 | /// 2 symbols. 13 | S2 = 0x1, 14 | /// 4 symbols. 15 | S4 = 0x2, 16 | /// 8 symbols. 17 | S8 = 0x3, 18 | /// 16 symbols. 19 | S16 = 0x4, 20 | } 21 | 22 | /// Mode to enter after a channel activity detection scan is finished. 23 | /// 24 | /// Argument of [`CadParams::set_exit_mode`]. 25 | #[derive(Debug, PartialEq, Eq)] 26 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 27 | #[repr(u8)] 28 | pub enum ExitMode { 29 | /// Standby with RC 13 MHz mode entry after CAD. 30 | Standby = 0, 31 | /// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected 32 | /// during the CAD scan. 33 | /// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode 34 | /// until a packet is received or until the CAD timeout is reached. 35 | StandbyLoRa = 1, 36 | } 37 | 38 | /// Channel activity detection (CAD) parameters. 39 | /// 40 | /// Argument of [`set_cad_params`]. 41 | /// 42 | /// # Recommended CAD settings 43 | /// 44 | /// This is taken directly from the datasheet. 45 | /// 46 | /// "The correct values selected in the table below must be carefully tested to 47 | /// ensure a good detection at sensitivity level and to limit the number of 48 | /// false detections" 49 | /// 50 | /// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] | 51 | /// |-----------------------|------------------|-----------------| 52 | /// | 5 | 0x18 | 0x10 | 53 | /// | 6 | 0x19 | 0x10 | 54 | /// | 7 | 0x20 | 0x10 | 55 | /// | 8 | 0x21 | 0x10 | 56 | /// | 9 | 0x22 | 0x10 | 57 | /// | 10 | 0x23 | 0x10 | 58 | /// | 11 | 0x24 | 0x10 | 59 | /// | 12 | 0x25 | 0x10 | 60 | /// 61 | /// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params 62 | /// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak 63 | /// [`set_det_min`]: crate::subghz::CadParams::set_det_min 64 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 65 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 66 | pub struct CadParams { 67 | buf: [u8; 8], 68 | } 69 | 70 | impl CadParams { 71 | /// Create a new `CadParams`. 72 | /// 73 | /// This is the same as `default`, but in a `const` function. 74 | /// 75 | /// # Example 76 | /// 77 | /// ``` 78 | /// use stm32wlxx_hal::subghz::CadParams; 79 | /// 80 | /// const CAD_PARAMS: CadParams = CadParams::new(); 81 | /// assert_eq!(CAD_PARAMS, CadParams::default()); 82 | /// ``` 83 | pub const fn new() -> CadParams { 84 | CadParams { 85 | buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0], 86 | } 87 | .set_num_symbol(NbCadSymbol::S1) 88 | .set_det_peak(0x18) 89 | .set_det_min(0x10) 90 | .set_exit_mode(ExitMode::Standby) 91 | } 92 | 93 | /// Number of symbols used for a CAD scan. 94 | /// 95 | /// # Example 96 | /// 97 | /// Set the number of symbols to 4. 98 | /// 99 | /// ``` 100 | /// use stm32wlxx_hal::subghz::{CadParams, NbCadSymbol}; 101 | /// 102 | /// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4); 103 | /// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2); 104 | /// ``` 105 | #[must_use = "set_num_symbol returns a modified CadParams"] 106 | pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams { 107 | self.buf[1] = nb as u8; 108 | self 109 | } 110 | 111 | /// Used with [`set_det_min`] to correlate the LoRa symbol. 112 | /// 113 | /// See the table in [`CadParams`] docs for recommended values. 114 | /// 115 | /// # Example 116 | /// 117 | /// Setting the recommended value for a spreading factor of 7. 118 | /// 119 | /// ``` 120 | /// use stm32wlxx_hal::subghz::CadParams; 121 | /// 122 | /// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10); 123 | /// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20); 124 | /// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10); 125 | /// ``` 126 | /// 127 | /// [`set_det_min`]: crate::subghz::CadParams::set_det_min 128 | #[must_use = "set_det_peak returns a modified CadParams"] 129 | pub const fn set_det_peak(mut self, peak: u8) -> CadParams { 130 | self.buf[2] = peak; 131 | self 132 | } 133 | 134 | /// Used with [`set_det_peak`] to correlate the LoRa symbol. 135 | /// 136 | /// See the table in [`CadParams`] docs for recommended values. 137 | /// 138 | /// # Example 139 | /// 140 | /// Setting the recommended value for a spreading factor of 6. 141 | /// 142 | /// ``` 143 | /// use stm32wlxx_hal::subghz::CadParams; 144 | /// 145 | /// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10); 146 | /// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18); 147 | /// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10); 148 | /// ``` 149 | /// 150 | /// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak 151 | #[must_use = "set_det_min returns a modified CadParams"] 152 | pub const fn set_det_min(mut self, min: u8) -> CadParams { 153 | self.buf[3] = min; 154 | self 155 | } 156 | 157 | /// Mode to enter after a channel activity detection scan is finished. 158 | /// 159 | /// # Example 160 | /// 161 | /// ``` 162 | /// use stm32wlxx_hal::subghz::{CadParams, ExitMode}; 163 | /// 164 | /// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby); 165 | /// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00); 166 | /// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01); 167 | /// ``` 168 | #[must_use = "set_exit_mode returns a modified CadParams"] 169 | pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams { 170 | self.buf[4] = mode as u8; 171 | self 172 | } 173 | 174 | /// Set the timeout. 175 | /// 176 | /// This is only used with [`ExitMode::StandbyLoRa`]. 177 | /// 178 | /// # Example 179 | /// 180 | /// ``` 181 | /// use stm32wlxx_hal::subghz::{CadParams, ExitMode, Timeout}; 182 | /// 183 | /// const TIMEOUT: Timeout = Timeout::from_raw(0x123456); 184 | /// const CAD_PARAMS: CadParams = CadParams::new() 185 | /// .set_exit_mode(ExitMode::StandbyLoRa) 186 | /// .set_timeout(TIMEOUT); 187 | /// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01); 188 | /// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12); 189 | /// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34); 190 | /// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56); 191 | /// ``` 192 | #[must_use = "set_timeout returns a modified CadParams"] 193 | pub const fn set_timeout(mut self, to: Timeout) -> CadParams { 194 | let to_bytes: [u8; 3] = to.as_bytes(); 195 | self.buf[5] = to_bytes[0]; 196 | self.buf[6] = to_bytes[1]; 197 | self.buf[7] = to_bytes[2]; 198 | self 199 | } 200 | 201 | /// Extracts a slice containing the packet. 202 | /// 203 | /// # Example 204 | /// 205 | /// ``` 206 | /// use stm32wlxx_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout}; 207 | /// 208 | /// const TIMEOUT: Timeout = Timeout::from_raw(0x123456); 209 | /// const CAD_PARAMS: CadParams = CadParams::new() 210 | /// .set_num_symbol(NbCadSymbol::S4) 211 | /// .set_det_peak(0x18) 212 | /// .set_det_min(0x10) 213 | /// .set_exit_mode(ExitMode::StandbyLoRa) 214 | /// .set_timeout(TIMEOUT); 215 | /// 216 | /// assert_eq!( 217 | /// CAD_PARAMS.as_slice(), 218 | /// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56] 219 | /// ); 220 | /// ``` 221 | pub const fn as_slice(&self) -> &[u8] { 222 | &self.buf 223 | } 224 | } 225 | 226 | impl Default for CadParams { 227 | fn default() -> Self { 228 | Self::new() 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /hal/src/subghz/calibrate.rs: -------------------------------------------------------------------------------- 1 | /// Image calibration. 2 | /// 3 | /// Argument of [`calibrate_image`]. 4 | /// 5 | /// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | pub struct CalibrateImage(pub(crate) u8, pub(crate) u8); 9 | 10 | impl CalibrateImage { 11 | /// Image calibration for the 430 - 440 MHz ISM band. 12 | pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F); 13 | 14 | /// Image calibration for the 470 - 510 MHz ISM band. 15 | pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81); 16 | 17 | /// Image calibration for the 779 - 787 MHz ISM band. 18 | pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5); 19 | 20 | /// Image calibration for the 863 - 870 MHz ISM band. 21 | pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB); 22 | 23 | /// Image calibration for the 902 - 928 MHz ISM band. 24 | pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9); 25 | 26 | /// Create a new `CalibrateImage` structure from raw values. 27 | /// 28 | /// # Example 29 | /// 30 | /// ``` 31 | /// use stm32wlxx_hal::subghz::CalibrateImage; 32 | /// 33 | /// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9); 34 | /// assert_eq!(CAL, CalibrateImage::ISM_902_928); 35 | /// ``` 36 | pub const fn new(f1: u8, f2: u8) -> CalibrateImage { 37 | CalibrateImage(f1, f2) 38 | } 39 | 40 | /// Create a new `CalibrateImage` structure from two frequencies. 41 | /// 42 | /// # Arguments 43 | /// 44 | /// The units for `freq1` and `freq2` are in MHz. 45 | /// 46 | /// # Panics 47 | /// 48 | /// * Panics if `freq1` is less than `freq2`. 49 | /// * Panics if `freq1` or `freq2` is not a multiple of 4MHz. 50 | /// * Panics if `freq1` or `freq2` is greater than `1020`. 51 | /// 52 | /// # Example 53 | /// 54 | /// Create an image calibration for the 430 - 440 MHz ISM band. 55 | /// 56 | /// ``` 57 | /// use stm32wlxx_hal::subghz::CalibrateImage; 58 | /// 59 | /// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444); 60 | /// assert_eq!(cal, CalibrateImage::ISM_430_440); 61 | /// ``` 62 | pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage { 63 | assert!(freq2 >= freq1); 64 | assert_eq!(freq1 % 4, 0); 65 | assert_eq!(freq2 % 4, 0); 66 | assert!(freq1 <= 1020); 67 | assert!(freq2 <= 1020); 68 | CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8) 69 | } 70 | } 71 | 72 | impl Default for CalibrateImage { 73 | fn default() -> Self { 74 | CalibrateImage::new(0xE1, 0xE9) 75 | } 76 | } 77 | 78 | /// Block calibration. 79 | /// 80 | /// Argument of [`calibrate`]. 81 | /// 82 | /// [`calibrate`]: crate::subghz::SubGhz::calibrate 83 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 84 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 85 | #[repr(u8)] 86 | pub enum Calibrate { 87 | /// Image calibration 88 | Image = 1 << 6, 89 | /// RF-ADC bulk P calibration 90 | AdcBulkP = 1 << 5, 91 | /// RF-ADC bulk N calibration 92 | AdcBulkN = 1 << 4, 93 | /// RF-ADC pulse calibration 94 | AdcPulse = 1 << 3, 95 | /// RF-PLL calibration 96 | Pll = 1 << 2, 97 | /// Sub-GHz radio RC 13 MHz calibration 98 | Rc13M = 1 << 1, 99 | /// Sub-GHz radio RC 64 kHz calibration 100 | Rc64K = 1, 101 | } 102 | 103 | impl Calibrate { 104 | /// Get the bitmask for the block calibration. 105 | /// 106 | /// # Example 107 | /// 108 | /// ``` 109 | /// use stm32wlxx_hal::subghz::Calibrate; 110 | /// 111 | /// assert_eq!(Calibrate::Image.mask(), 0b0100_0000); 112 | /// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000); 113 | /// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000); 114 | /// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000); 115 | /// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100); 116 | /// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010); 117 | /// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001); 118 | /// ``` 119 | pub const fn mask(self) -> u8 { 120 | self as u8 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /hal/src/subghz/fallback_mode.rs: -------------------------------------------------------------------------------- 1 | /// Fallback mode after successful packet transmission or packet reception. 2 | /// 3 | /// Argument of [`set_tx_rx_fallback_mode`]. 4 | /// 5 | /// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode. 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | #[repr(u8)] 9 | pub enum FallbackMode { 10 | /// Standby mode entry. 11 | Standby = 0x20, 12 | /// Standby with HSE32 enabled. 13 | StandbyHse = 0x30, 14 | /// Frequency synthesizer entry. 15 | Fs = 0x40, 16 | } 17 | 18 | impl From for u8 { 19 | fn from(fm: FallbackMode) -> Self { 20 | fm as u8 21 | } 22 | } 23 | 24 | impl Default for FallbackMode { 25 | /// Default fallback mode after power-on reset. 26 | /// 27 | /// # Example 28 | /// 29 | /// ``` 30 | /// use stm32wlxx_hal::subghz::FallbackMode; 31 | /// 32 | /// assert_eq!(FallbackMode::default(), FallbackMode::Standby); 33 | /// ``` 34 | fn default() -> Self { 35 | FallbackMode::Standby 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /hal/src/subghz/hse_trim.rs: -------------------------------------------------------------------------------- 1 | use super::ValueError; 2 | 3 | /// HSE32 load capacitor trimming. 4 | /// 5 | /// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`]. 6 | /// 7 | /// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim 8 | /// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim 9 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 11 | pub struct HseTrim { 12 | val: u8, 13 | } 14 | 15 | impl HseTrim { 16 | /// Maximum capacitor value, ~33.4 pF 17 | pub const MAX: HseTrim = HseTrim::from_raw(0x2F); 18 | 19 | /// Minimum capacitor value, ~11.3 pF 20 | pub const MIN: HseTrim = HseTrim::from_raw(0x00); 21 | 22 | /// Power-on-reset capacitor value, ~20.3 pF 23 | /// 24 | /// This is the same as `default`. 25 | /// 26 | /// # Example 27 | /// 28 | /// ``` 29 | /// use stm32wlxx_hal::subghz::HseTrim; 30 | /// 31 | /// assert_eq!(HseTrim::POR, HseTrim::default()); 32 | /// ``` 33 | pub const POR: HseTrim = HseTrim::from_raw(0x12); 34 | 35 | /// Create a new [`HseTrim`] structure from a raw value. 36 | /// 37 | /// Values greater than the maximum of `0x2F` will be set to the maximum. 38 | /// 39 | /// # Example 40 | /// 41 | /// ``` 42 | /// use stm32wlxx_hal::subghz::HseTrim; 43 | /// 44 | /// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX); 45 | /// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX); 46 | /// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN); 47 | /// ``` 48 | pub const fn from_raw(raw: u8) -> HseTrim { 49 | if raw > 0x2F { 50 | HseTrim { val: 0x2F } 51 | } else { 52 | HseTrim { val: raw } 53 | } 54 | } 55 | 56 | /// Create a HSE trim value from farads. 57 | /// 58 | /// Values greater than the maximum of 33.4 pF will be set to the maximum. 59 | /// Values less than the minimum of 11.3 pF will be set to the minimum. 60 | /// 61 | /// # Example 62 | /// 63 | /// ``` 64 | /// use stm32wlxx_hal::subghz::HseTrim; 65 | /// 66 | /// assert!(HseTrim::from_farads(1.0).is_err()); 67 | /// assert!(HseTrim::from_farads(1e-12).is_err()); 68 | /// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default())); 69 | /// ``` 70 | pub fn from_farads(farads: f32) -> Result> { 71 | const MAX: f32 = 33.4E-12; 72 | const MIN: f32 = 11.3E-12; 73 | if farads > MAX { 74 | Err(ValueError::too_high(farads, MAX)) 75 | } else if farads < MIN { 76 | Err(ValueError::too_low(farads, MIN)) 77 | } else { 78 | Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8)) 79 | } 80 | } 81 | 82 | /// Get the capacitance as farads. 83 | /// 84 | /// # Example 85 | /// 86 | /// ``` 87 | /// use stm32wlxx_hal::subghz::HseTrim; 88 | /// 89 | /// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33); 90 | /// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11); 91 | /// ``` 92 | pub fn as_farads(&self) -> f32 { 93 | (self.val as f32) * 0.47E-12 + 11.3E-12 94 | } 95 | } 96 | 97 | impl From for u8 { 98 | fn from(ht: HseTrim) -> Self { 99 | ht.val 100 | } 101 | } 102 | 103 | impl Default for HseTrim { 104 | fn default() -> Self { 105 | Self::POR 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /hal/src/subghz/lora_sync_word.rs: -------------------------------------------------------------------------------- 1 | /// LoRa synchronization word. 2 | /// 3 | /// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word]. 4 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 6 | pub enum LoRaSyncWord { 7 | /// LoRa private network. 8 | Private, 9 | /// LoRa public network. 10 | Public, 11 | /// Custom sync word 12 | Custom([u8; 2]), 13 | } 14 | 15 | impl LoRaSyncWord { 16 | pub(crate) const fn bytes(self) -> [u8; 2] { 17 | match self { 18 | LoRaSyncWord::Private => [0x14, 0x24], 19 | LoRaSyncWord::Public => [0x34, 0x44], 20 | LoRaSyncWord::Custom(buf) => buf, 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hal/src/subghz/ocp.rs: -------------------------------------------------------------------------------- 1 | /// Power amplifier over current protection. 2 | /// 3 | /// Used by [`set_pa_ocp`]. 4 | /// 5 | /// [`set_pa_ocp`]: super::SubGhz::set_pa_ocp 6 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | #[repr(u8)] 9 | pub enum Ocp { 10 | /// Maximum 60mA current for LP PA mode. 11 | Max60m = 0x18, 12 | /// Maximum 140mA for HP PA mode. 13 | Max140m = 0x38, 14 | } 15 | -------------------------------------------------------------------------------- /hal/src/subghz/op_error.rs: -------------------------------------------------------------------------------- 1 | /// Operation Errors. 2 | /// 3 | /// Returned by [`op_error`]. 4 | /// 5 | /// [`op_error`]: super::SubGhz::op_error 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | #[repr(u8)] 9 | pub enum OpError { 10 | /// PA ramping failed 11 | PaRampError = 8, 12 | /// RF-PLL locking failed 13 | PllLockError = 6, 14 | /// HSE32 clock startup failed 15 | XoscStartError = 5, 16 | /// Image calibration failed 17 | ImageCalibrationError = 4, 18 | /// RF-ADC calibration failed 19 | AdcCalibrationError = 3, 20 | /// RF-PLL calibration failed 21 | PllCalibrationError = 2, 22 | /// Sub-GHz radio RC 13 MHz oscillator 23 | RC13MCalibrationError = 1, 24 | /// Sub-GHz radio RC 64 kHz oscillator 25 | RC64KCalibrationError = 0, 26 | } 27 | 28 | impl OpError { 29 | /// Get the bitmask for the error. 30 | /// 31 | /// # Example 32 | /// 33 | /// ``` 34 | /// use stm32wlxx_hal::subghz::OpError; 35 | /// 36 | /// assert_eq!(OpError::PaRampError.mask(), 0b1_0000_0000); 37 | /// assert_eq!(OpError::PllLockError.mask(), 0b0_0100_0000); 38 | /// assert_eq!(OpError::XoscStartError.mask(), 0b0_0010_0000); 39 | /// assert_eq!(OpError::ImageCalibrationError.mask(), 0b0_0001_0000); 40 | /// assert_eq!(OpError::AdcCalibrationError.mask(), 0b0_0000_1000); 41 | /// assert_eq!(OpError::PllCalibrationError.mask(), 0b0_0000_0100); 42 | /// assert_eq!(OpError::RC13MCalibrationError.mask(), 0b0_0000_0010); 43 | /// assert_eq!(OpError::RC64KCalibrationError.mask(), 0b0_0000_0001); 44 | /// ``` 45 | pub const fn mask(self) -> u16 { 46 | 1 << (self as u8) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /hal/src/subghz/pa_config.rs: -------------------------------------------------------------------------------- 1 | /// Power amplifier configuration parameters. 2 | /// 3 | /// Argument of [`set_pa_config`]. 4 | /// 5 | /// [`set_pa_config`]: super::SubGhz::set_pa_config 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | pub struct PaConfig { 9 | buf: [u8; 5], 10 | } 11 | 12 | impl PaConfig { 13 | /// Optimal settings for +15dBm output power with the low-power PA. 14 | /// 15 | /// This must be used with [`TxParams::LP_15`](super::TxParams::LP_15). 16 | pub const LP_15: PaConfig = PaConfig::new() 17 | .set_pa_duty_cycle(0x6) 18 | .set_hp_max(0x0) 19 | .set_pa(PaSel::Lp); 20 | 21 | /// Optimal settings for +14dBm output power with the low-power PA. 22 | /// 23 | /// This must be used with [`TxParams::LP_14`](super::TxParams::LP_14). 24 | pub const LP_14: PaConfig = PaConfig::new() 25 | .set_pa_duty_cycle(0x4) 26 | .set_hp_max(0x0) 27 | .set_pa(PaSel::Lp); 28 | 29 | /// Optimal settings for +10dBm output power with the low-power PA. 30 | /// 31 | /// This must be used with [`TxParams::LP_10`](super::TxParams::LP_10). 32 | pub const LP_10: PaConfig = PaConfig::new() 33 | .set_pa_duty_cycle(0x1) 34 | .set_hp_max(0x0) 35 | .set_pa(PaSel::Lp); 36 | 37 | /// Optimal settings for +22dBm output power with the high-power PA. 38 | /// 39 | /// This must be used with [`TxParams::HP`](super::TxParams::HP). 40 | pub const HP_22: PaConfig = PaConfig::new() 41 | .set_pa_duty_cycle(0x4) 42 | .set_hp_max(0x7) 43 | .set_pa(PaSel::Hp); 44 | 45 | /// Optimal settings for +20dBm output power with the high-power PA. 46 | /// 47 | /// This must be used with [`TxParams::HP`](super::TxParams::HP). 48 | pub const HP_20: PaConfig = PaConfig::new() 49 | .set_pa_duty_cycle(0x3) 50 | .set_hp_max(0x5) 51 | .set_pa(PaSel::Hp); 52 | 53 | /// Optimal settings for +17dBm output power with the high-power PA. 54 | /// 55 | /// This must be used with [`TxParams::HP`](super::TxParams::HP). 56 | pub const HP_17: PaConfig = PaConfig::new() 57 | .set_pa_duty_cycle(0x2) 58 | .set_hp_max(0x3) 59 | .set_pa(PaSel::Hp); 60 | 61 | /// Optimal settings for +14dBm output power with the high-power PA. 62 | /// 63 | /// This must be used with [`TxParams::HP`](super::TxParams::HP). 64 | pub const HP_14: PaConfig = PaConfig::new() 65 | .set_pa_duty_cycle(0x2) 66 | .set_hp_max(0x2) 67 | .set_pa(PaSel::Hp); 68 | 69 | /// Create a new `PaConfig` struct. 70 | /// 71 | /// This is the same as `default`, but in a `const` function. 72 | /// 73 | /// # Example 74 | /// 75 | /// ``` 76 | /// use stm32wlxx_hal::subghz::PaConfig; 77 | /// 78 | /// const PA_CONFIG: PaConfig = PaConfig::new(); 79 | /// ``` 80 | pub const fn new() -> PaConfig { 81 | PaConfig { 82 | buf: [super::OpCode::SetPaConfig as u8, 0x01, 0x00, 0x01, 0x01], 83 | } 84 | } 85 | 86 | /// Set the power amplifier duty cycle (conduit angle) control. 87 | /// 88 | /// **Note:** Only the first 3 bits of the `pa_duty_cycle` argument are used. 89 | /// 90 | /// Duty cycle = 0.2 + 0.04 × bits 91 | /// 92 | /// # Caution 93 | /// 94 | /// The following restrictions must be observed to avoid over-stress on the PA: 95 | /// * LP PA mode with synthesis frequency > 400 MHz, `pa_duty_cycle` must be < 0x7. 96 | /// * LP PA mode with synthesis frequency < 400 MHz, `pa_duty_cycle` must be < 0x4. 97 | /// * HP PA mode, `pa_duty_cycle` must be < 0x4 98 | /// 99 | /// # Example 100 | /// 101 | /// ``` 102 | /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; 103 | /// 104 | /// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Lp).set_pa_duty_cycle(0x4); 105 | /// # assert_eq!(PA_CONFIG.as_slice()[1], 0x04); 106 | /// ``` 107 | #[must_use = "set_pa_duty_cycle returns a modified PaConfig"] 108 | pub const fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> PaConfig { 109 | self.buf[1] = pa_duty_cycle & 0b111; 110 | self 111 | } 112 | 113 | /// Set the high power amplifier output power. 114 | /// 115 | /// **Note:** Only the first 3 bits of the `hp_max` argument are used. 116 | /// 117 | /// # Example 118 | /// 119 | /// ``` 120 | /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; 121 | /// 122 | /// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Hp).set_hp_max(0x2); 123 | /// # assert_eq!(PA_CONFIG.as_slice()[2], 0x02); 124 | /// ``` 125 | #[must_use = "set_hp_max returns a modified PaConfig"] 126 | pub const fn set_hp_max(mut self, hp_max: u8) -> PaConfig { 127 | self.buf[2] = hp_max & 0b111; 128 | self 129 | } 130 | 131 | /// Set the power amplifier to use, low or high power. 132 | /// 133 | /// # Example 134 | /// 135 | /// ``` 136 | /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; 137 | /// 138 | /// const PA_CONFIG_HP: PaConfig = PaConfig::new().set_pa(PaSel::Hp); 139 | /// const PA_CONFIG_LP: PaConfig = PaConfig::new().set_pa(PaSel::Lp); 140 | /// # assert_eq!(PA_CONFIG_HP.as_slice()[3], 0x00); 141 | /// # assert_eq!(PA_CONFIG_LP.as_slice()[3], 0x01); 142 | /// ``` 143 | #[must_use = "set_pa returns a modified PaConfig"] 144 | pub const fn set_pa(mut self, pa: PaSel) -> PaConfig { 145 | self.buf[3] = pa as u8; 146 | self 147 | } 148 | 149 | /// Extracts a slice containing the packet. 150 | /// 151 | /// # Example 152 | /// 153 | /// ``` 154 | /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; 155 | /// 156 | /// const PA_CONFIG: PaConfig = PaConfig::new() 157 | /// .set_pa(PaSel::Hp) 158 | /// .set_pa_duty_cycle(0x2) 159 | /// .set_hp_max(0x3); 160 | /// 161 | /// assert_eq!(PA_CONFIG.as_slice(), &[0x95, 0x2, 0x03, 0x00, 0x01]); 162 | /// ``` 163 | pub const fn as_slice(&self) -> &[u8] { 164 | &self.buf 165 | } 166 | } 167 | 168 | impl Default for PaConfig { 169 | fn default() -> Self { 170 | Self::new() 171 | } 172 | } 173 | 174 | /// Power amplifier selection. 175 | /// 176 | /// Argument of [`PaConfig::set_pa`]. 177 | #[repr(u8)] 178 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] 179 | pub enum PaSel { 180 | /// High power amplifier. 181 | Hp = 0b0, 182 | /// Low power amplifier. 183 | #[default] 184 | Lp = 0b1, 185 | } 186 | 187 | impl PartialOrd for PaSel { 188 | fn partial_cmp(&self, other: &Self) -> Option { 189 | Some(self.cmp(other)) 190 | } 191 | } 192 | 193 | impl Ord for PaSel { 194 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { 195 | match (self, other) { 196 | (PaSel::Hp, PaSel::Hp) | (PaSel::Lp, PaSel::Lp) => core::cmp::Ordering::Equal, 197 | (PaSel::Hp, PaSel::Lp) => core::cmp::Ordering::Greater, 198 | (PaSel::Lp, PaSel::Hp) => core::cmp::Ordering::Less, 199 | } 200 | } 201 | } 202 | 203 | #[cfg(test)] 204 | mod test { 205 | use super::PaSel; 206 | 207 | #[test] 208 | fn pa_sel_ord() { 209 | assert!(PaSel::Lp < PaSel::Hp); 210 | assert!(PaSel::Hp > PaSel::Lp); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /hal/src/subghz/packet_type.rs: -------------------------------------------------------------------------------- 1 | /// Packet type definition. 2 | /// 3 | /// Argument of [`set_packet_type`] 4 | /// 5 | /// [`set_packet_type`]: super::SubGhz::set_packet_type 6 | #[repr(u8)] 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 9 | pub enum PacketType { 10 | /// FSK (frequency shift keying) generic packet type. 11 | Fsk = 0, 12 | /// LoRa (long range) packet type. 13 | LoRa = 1, 14 | /// BPSK (binary phase shift keying) packet type. 15 | Bpsk = 2, 16 | /// MSK (minimum shift keying) generic packet type. 17 | Msk = 3, 18 | } 19 | 20 | impl PacketType { 21 | /// Create a new `PacketType` from bits. 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// use stm32wlxx_hal::subghz::PacketType; 27 | /// 28 | /// assert_eq!(PacketType::from_raw(0), Ok(PacketType::Fsk)); 29 | /// assert_eq!(PacketType::from_raw(1), Ok(PacketType::LoRa)); 30 | /// assert_eq!(PacketType::from_raw(2), Ok(PacketType::Bpsk)); 31 | /// assert_eq!(PacketType::from_raw(3), Ok(PacketType::Msk)); 32 | /// // Other values are reserved 33 | /// assert_eq!(PacketType::from_raw(4), Err(4)); 34 | /// ``` 35 | pub const fn from_raw(bits: u8) -> Result { 36 | match bits { 37 | 0 => Ok(PacketType::Fsk), 38 | 1 => Ok(PacketType::LoRa), 39 | 2 => Ok(PacketType::Bpsk), 40 | 3 => Ok(PacketType::Msk), 41 | _ => Err(bits), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /hal/src/subghz/pkt_ctrl.rs: -------------------------------------------------------------------------------- 1 | /// Generic packet infinite sequence selection. 2 | /// 3 | /// Argument of [`PktCtrl::set_inf_seq_sel`]. 4 | #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 6 | pub enum InfSeqSel { 7 | /// Preamble `0x5555`. 8 | #[default] 9 | Five = 0b00, 10 | /// Preamble `0x0000`. 11 | Zero = 0b01, 12 | /// Preamble `0xFFFF`. 13 | One = 0b10, 14 | /// PRBS9. 15 | Prbs9 = 0b11, 16 | } 17 | 18 | /// Generic packet control. 19 | /// 20 | /// Argument of [`set_pkt_ctrl`](super::SubGhz::set_pkt_ctrl). 21 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 22 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 23 | pub struct PktCtrl { 24 | val: u8, 25 | } 26 | 27 | impl PktCtrl { 28 | /// Reset value of the packet control register. 29 | pub const RESET: PktCtrl = PktCtrl { val: 0x21 }; 30 | 31 | /// Create a new [`PktCtrl`] structure from a raw value. 32 | /// 33 | /// Reserved bits will be masked. 34 | pub const fn from_raw(raw: u8) -> Self { 35 | Self { val: raw & 0x3F } 36 | } 37 | 38 | /// Get the raw value of the [`PktCtrl`] register. 39 | pub const fn as_bits(&self) -> u8 { 40 | self.val 41 | } 42 | 43 | /// Generic packet synchronization word detection enable. 44 | /// 45 | /// # Example 46 | /// 47 | /// ``` 48 | /// use stm32wlxx_hal::subghz::PktCtrl; 49 | /// 50 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_sync_det_en(true); 51 | /// ``` 52 | #[must_use = "set_sync_det_en returns a modified PktCtrl"] 53 | pub const fn set_sync_det_en(mut self, en: bool) -> PktCtrl { 54 | if en { 55 | self.val |= 1 << 5; 56 | } else { 57 | self.val &= !(1 << 5); 58 | } 59 | self 60 | } 61 | 62 | /// Returns `true` if generic packet synchronization word detection is 63 | /// enabled. 64 | /// 65 | /// # Example 66 | /// 67 | /// ``` 68 | /// use stm32wlxx_hal::subghz::PktCtrl; 69 | /// 70 | /// let pc: PktCtrl = PktCtrl::RESET; 71 | /// assert_eq!(pc.sync_det_en(), true); 72 | /// let pc: PktCtrl = pc.set_sync_det_en(false); 73 | /// assert_eq!(pc.sync_det_en(), false); 74 | /// let pc: PktCtrl = pc.set_sync_det_en(true); 75 | /// assert_eq!(pc.sync_det_en(), true); 76 | /// ``` 77 | pub const fn sync_det_en(&self) -> bool { 78 | self.val & (1 << 5) != 0 79 | } 80 | 81 | /// Generic packet continuous transmit enable. 82 | /// 83 | /// # Example 84 | /// 85 | /// ``` 86 | /// use stm32wlxx_hal::subghz::PktCtrl; 87 | /// 88 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_cont_tx_en(true); 89 | /// ``` 90 | #[must_use = "set_cont_tx_en returns a modified PktCtrl"] 91 | pub const fn set_cont_tx_en(mut self, en: bool) -> PktCtrl { 92 | if en { 93 | self.val |= 1 << 4; 94 | } else { 95 | self.val &= !(1 << 4); 96 | } 97 | self 98 | } 99 | 100 | /// Returns `true` if generic packet continuous transmit is enabled. 101 | /// 102 | /// # Example 103 | /// 104 | /// ``` 105 | /// use stm32wlxx_hal::subghz::PktCtrl; 106 | /// 107 | /// let pc: PktCtrl = PktCtrl::RESET; 108 | /// assert_eq!(pc.cont_tx_en(), false); 109 | /// let pc: PktCtrl = pc.set_cont_tx_en(true); 110 | /// assert_eq!(pc.cont_tx_en(), true); 111 | /// let pc: PktCtrl = pc.set_cont_tx_en(false); 112 | /// assert_eq!(pc.cont_tx_en(), false); 113 | /// ``` 114 | pub const fn cont_tx_en(&self) -> bool { 115 | self.val & (1 << 4) != 0 116 | } 117 | 118 | /// Set the continuous sequence type. 119 | #[must_use = "set_inf_seq_sel returns a modified PktCtrl"] 120 | pub const fn set_inf_seq_sel(mut self, sel: InfSeqSel) -> PktCtrl { 121 | self.val &= !(0b11 << 2); 122 | self.val |= (sel as u8) << 2; 123 | self 124 | } 125 | 126 | /// Get the continuous sequence type. 127 | /// 128 | /// # Example 129 | /// 130 | /// ``` 131 | /// use stm32wlxx_hal::subghz::{InfSeqSel, PktCtrl}; 132 | /// 133 | /// let pc: PktCtrl = PktCtrl::RESET; 134 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five); 135 | /// 136 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Zero); 137 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Zero); 138 | /// 139 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::One); 140 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::One); 141 | /// 142 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Prbs9); 143 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Prbs9); 144 | /// 145 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Five); 146 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five); 147 | /// ``` 148 | pub const fn inf_seq_sel(&self) -> InfSeqSel { 149 | match (self.val >> 2) & 0b11 { 150 | 0b00 => InfSeqSel::Five, 151 | 0b01 => InfSeqSel::Zero, 152 | 0b10 => InfSeqSel::One, 153 | _ => InfSeqSel::Prbs9, 154 | } 155 | } 156 | 157 | /// Enable infinite sequence generation. 158 | /// 159 | /// # Example 160 | /// 161 | /// ``` 162 | /// use stm32wlxx_hal::subghz::PktCtrl; 163 | /// 164 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_inf_seq_en(true); 165 | /// ``` 166 | #[must_use = "set_inf_seq_en returns a modified PktCtrl"] 167 | pub const fn set_inf_seq_en(mut self, en: bool) -> PktCtrl { 168 | if en { 169 | self.val |= 1 << 1; 170 | } else { 171 | self.val &= !(1 << 1); 172 | } 173 | self 174 | } 175 | 176 | /// Returns `true` if infinite sequence generation is enabled. 177 | /// 178 | /// # Example 179 | /// 180 | /// ``` 181 | /// use stm32wlxx_hal::subghz::PktCtrl; 182 | /// 183 | /// let pc: PktCtrl = PktCtrl::RESET; 184 | /// assert_eq!(pc.inf_seq_en(), false); 185 | /// let pc: PktCtrl = pc.set_inf_seq_en(true); 186 | /// assert_eq!(pc.inf_seq_en(), true); 187 | /// let pc: PktCtrl = pc.set_inf_seq_en(false); 188 | /// assert_eq!(pc.inf_seq_en(), false); 189 | /// ``` 190 | pub const fn inf_seq_en(&self) -> bool { 191 | self.val & (1 << 1) != 0 192 | } 193 | 194 | /// Set the value of bit-8 (9th bit) for generic packet whitening. 195 | /// 196 | /// # Example 197 | /// 198 | /// ``` 199 | /// use stm32wlxx_hal::subghz::PktCtrl; 200 | /// 201 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_whitening_init(true); 202 | /// ``` 203 | #[must_use = "set_whitening_init returns a modified PktCtrl"] 204 | pub const fn set_whitening_init(mut self, val: bool) -> PktCtrl { 205 | if val { 206 | self.val |= 1; 207 | } else { 208 | self.val &= !1; 209 | } 210 | self 211 | } 212 | 213 | /// Returns `true` if bit-8 of the generic packet whitening is set. 214 | /// 215 | /// # Example 216 | /// 217 | /// ``` 218 | /// use stm32wlxx_hal::subghz::PktCtrl; 219 | /// 220 | /// let pc: PktCtrl = PktCtrl::RESET; 221 | /// assert_eq!(pc.whitening_init(), true); 222 | /// let pc: PktCtrl = pc.set_whitening_init(false); 223 | /// assert_eq!(pc.whitening_init(), false); 224 | /// let pc: PktCtrl = pc.set_whitening_init(true); 225 | /// assert_eq!(pc.whitening_init(), true); 226 | /// ``` 227 | pub const fn whitening_init(&self) -> bool { 228 | self.val & 0b1 != 0 229 | } 230 | } 231 | 232 | impl From for u8 { 233 | fn from(pc: PktCtrl) -> Self { 234 | pc.val 235 | } 236 | } 237 | 238 | impl Default for PktCtrl { 239 | fn default() -> Self { 240 | Self::RESET 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /hal/src/subghz/pmode.rs: -------------------------------------------------------------------------------- 1 | /// RX gain power modes. 2 | /// 3 | /// Argument of [`set_rx_gain`]. 4 | /// 5 | /// [`set_rx_gain`]: super::SubGhz::set_rx_gain 6 | #[repr(u8)] 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 9 | pub enum PMode { 10 | /// Power saving mode. 11 | /// 12 | /// Reduces sensitivity. 13 | #[allow(clippy::identity_op)] 14 | PowerSaving = (0x25 << 2) | 0b00, 15 | /// Boost mode level 1. 16 | /// 17 | /// Improves sensitivity at detriment of power consumption. 18 | Boost1 = (0x25 << 2) | 0b01, 19 | /// Boost mode level 2. 20 | /// 21 | /// Improves a set further sensitivity at detriment of power consumption. 22 | Boost2 = (0x25 << 2) | 0b10, 23 | /// Boost mode. 24 | /// 25 | /// Best receiver sensitivity. 26 | Boost = (0x25 << 2) | 0b11, 27 | } 28 | -------------------------------------------------------------------------------- /hal/src/subghz/pwr_ctrl.rs: -------------------------------------------------------------------------------- 1 | /// Power-supply current limit. 2 | /// 3 | /// Argument of [`PwrCtrl::set_current_lim`]. 4 | #[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 6 | #[repr(u8)] 7 | pub enum CurrentLim { 8 | /// 25 mA 9 | Milli25 = 0x0, 10 | /// 50 mA (default) 11 | #[default] 12 | Milli50 = 0x1, 13 | /// 100 mA 14 | Milli100 = 0x2, 15 | /// 200 mA 16 | Milli200 = 0x3, 17 | } 18 | 19 | impl CurrentLim { 20 | /// Get the SMPS drive value as milliamps. 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// use stm32wlxx_hal::subghz::CurrentLim; 26 | /// 27 | /// assert_eq!(CurrentLim::Milli25.as_milliamps(), 25); 28 | /// assert_eq!(CurrentLim::Milli50.as_milliamps(), 50); 29 | /// assert_eq!(CurrentLim::Milli100.as_milliamps(), 100); 30 | /// assert_eq!(CurrentLim::Milli200.as_milliamps(), 200); 31 | /// ``` 32 | pub const fn as_milliamps(&self) -> u8 { 33 | match self { 34 | CurrentLim::Milli25 => 25, 35 | CurrentLim::Milli50 => 50, 36 | CurrentLim::Milli100 => 100, 37 | CurrentLim::Milli200 => 200, 38 | } 39 | } 40 | } 41 | 42 | /// Power control. 43 | /// 44 | /// Argument of [`set_bit_sync`](super::SubGhz::set_bit_sync). 45 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 46 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 47 | pub struct PwrCtrl { 48 | val: u8, 49 | } 50 | 51 | impl PwrCtrl { 52 | /// Power control register reset value. 53 | pub const RESET: PwrCtrl = PwrCtrl { val: 0x50 }; 54 | 55 | /// Create a new [`PwrCtrl`] structure from a raw value. 56 | /// 57 | /// Reserved bits will be masked. 58 | pub const fn from_raw(raw: u8) -> Self { 59 | Self { val: raw & 0x70 } 60 | } 61 | 62 | /// Get the raw value of the [`PwrCtrl`] register. 63 | pub const fn as_bits(&self) -> u8 { 64 | self.val 65 | } 66 | 67 | /// Set the current limiter enable. 68 | /// 69 | /// # Example 70 | /// 71 | /// ``` 72 | /// use stm32wlxx_hal::subghz::PwrCtrl; 73 | /// 74 | /// const PWR_CTRL: PwrCtrl = PwrCtrl::RESET.set_current_lim_en(true); 75 | /// # assert_eq!(u8::from(PWR_CTRL), 0x50u8); 76 | /// ``` 77 | #[must_use = "set_current_lim_en returns a modified PwrCtrl"] 78 | pub const fn set_current_lim_en(mut self, en: bool) -> PwrCtrl { 79 | if en { 80 | self.val |= 1 << 6; 81 | } else { 82 | self.val &= !(1 << 6); 83 | } 84 | self 85 | } 86 | 87 | /// Returns `true` if current limiting is enabled 88 | /// 89 | /// # Example 90 | /// 91 | /// ``` 92 | /// use stm32wlxx_hal::subghz::PwrCtrl; 93 | /// 94 | /// let pc: PwrCtrl = PwrCtrl::RESET; 95 | /// assert_eq!(pc.current_limit_en(), true); 96 | /// let pc: PwrCtrl = pc.set_current_lim_en(false); 97 | /// assert_eq!(pc.current_limit_en(), false); 98 | /// let pc: PwrCtrl = pc.set_current_lim_en(true); 99 | /// assert_eq!(pc.current_limit_en(), true); 100 | /// ``` 101 | pub const fn current_limit_en(&self) -> bool { 102 | self.val & (1 << 6) != 0 103 | } 104 | 105 | /// Set the current limit. 106 | #[must_use = "set_current_lim returns a modified PwrCtrl"] 107 | pub const fn set_current_lim(mut self, lim: CurrentLim) -> PwrCtrl { 108 | self.val &= !(0x30); 109 | self.val |= (lim as u8) << 4; 110 | self 111 | } 112 | 113 | /// Get the current limit. 114 | /// 115 | /// # Example 116 | /// 117 | /// ``` 118 | /// use stm32wlxx_hal::subghz::{CurrentLim, PwrCtrl}; 119 | /// 120 | /// let pc: PwrCtrl = PwrCtrl::RESET; 121 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli50); 122 | /// 123 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli25); 124 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli25); 125 | /// 126 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli50); 127 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli50); 128 | /// 129 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli100); 130 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli100); 131 | /// 132 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli200); 133 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli200); 134 | /// ``` 135 | pub const fn current_lim(&self) -> CurrentLim { 136 | match (self.val >> 4) & 0b11 { 137 | 0x0 => CurrentLim::Milli25, 138 | 0x1 => CurrentLim::Milli50, 139 | 0x2 => CurrentLim::Milli100, 140 | _ => CurrentLim::Milli200, 141 | } 142 | } 143 | } 144 | 145 | impl From for u8 { 146 | fn from(bs: PwrCtrl) -> Self { 147 | bs.val 148 | } 149 | } 150 | 151 | impl Default for PwrCtrl { 152 | fn default() -> Self { 153 | Self::RESET 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /hal/src/subghz/reg_mode.rs: -------------------------------------------------------------------------------- 1 | /// Radio power supply selection. 2 | #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 3 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 4 | #[repr(u8)] 5 | pub enum RegMode { 6 | /// Linear dropout regulator 7 | #[default] 8 | Ldo = 0b0, 9 | /// Switch mode power supply. 10 | /// 11 | /// Used in standby with HSE32, FS, RX, and TX modes. 12 | Smps = 0b1, 13 | } 14 | -------------------------------------------------------------------------------- /hal/src/subghz/rf_frequency.rs: -------------------------------------------------------------------------------- 1 | /// RF frequency structure. 2 | /// 3 | /// Argument of [`set_rf_frequency`]. 4 | /// 5 | /// [`set_rf_frequency`]: super::SubGhz::set_rf_frequency 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | pub struct RfFreq { 9 | buf: [u8; 5], 10 | } 11 | 12 | impl RfFreq { 13 | /// 915MHz, often used in Australia and North America. 14 | /// 15 | /// # Example 16 | /// 17 | /// ``` 18 | /// use stm32wlxx_hal::subghz::RfFreq; 19 | /// 20 | /// assert_eq!(RfFreq::F915.freq(), 915_000_000); 21 | /// ``` 22 | pub const F915: RfFreq = RfFreq::from_raw(0x39_30_00_00); 23 | 24 | /// 868MHz, often used in Europe. 25 | /// 26 | /// # Example 27 | /// 28 | /// ``` 29 | /// use stm32wlxx_hal::subghz::RfFreq; 30 | /// 31 | /// assert_eq!(RfFreq::F868.freq(), 868_000_000); 32 | /// ``` 33 | pub const F868: RfFreq = RfFreq::from_raw(0x36_40_00_00); 34 | 35 | /// 433MHz, often used in Europe. 36 | /// 37 | /// # Example 38 | /// 39 | /// ``` 40 | /// use stm32wlxx_hal::subghz::RfFreq; 41 | /// 42 | /// assert_eq!(RfFreq::F433.freq(), 433_000_000); 43 | /// ``` 44 | pub const F433: RfFreq = RfFreq::from_raw(0x1B_10_00_00); 45 | 46 | /// Create a new `RfFreq` from a raw bit value. 47 | /// 48 | /// The equation used to get the PLL frequency from the raw bits is: 49 | /// 50 | /// RFPLL = 32e6 × bits / 225 51 | /// 52 | /// # Example 53 | /// 54 | /// ``` 55 | /// use stm32wlxx_hal::subghz::RfFreq; 56 | /// 57 | /// const FREQ: RfFreq = RfFreq::from_raw(0x39300000); 58 | /// assert_eq!(FREQ, RfFreq::F915); 59 | /// ``` 60 | pub const fn from_raw(bits: u32) -> RfFreq { 61 | RfFreq { 62 | buf: [ 63 | super::OpCode::SetRfFrequency as u8, 64 | ((bits >> 24) & 0xFF) as u8, 65 | ((bits >> 16) & 0xFF) as u8, 66 | ((bits >> 8) & 0xFF) as u8, 67 | (bits & 0xFF) as u8, 68 | ], 69 | } 70 | } 71 | 72 | /// Create a new `RfFreq` from a PLL frequency. 73 | /// 74 | /// The equation used to get the raw bits from the PLL frequency is: 75 | /// 76 | /// bits = RFPLL * 225 / 32e6 77 | /// 78 | /// # Example 79 | /// 80 | /// ``` 81 | /// use stm32wlxx_hal::subghz::RfFreq; 82 | /// 83 | /// const FREQ: RfFreq = RfFreq::from_frequency(915_000_000); 84 | /// assert_eq!(FREQ, RfFreq::F915); 85 | /// ``` 86 | pub const fn from_frequency(freq: u32) -> RfFreq { 87 | Self::from_raw((((freq as u64) * (1 << 25)) / 32_000_000) as u32) 88 | } 89 | 90 | // Get the frequency bit value. 91 | const fn as_bits(&self) -> u32 { 92 | ((self.buf[1] as u32) << 24) 93 | | ((self.buf[2] as u32) << 16) 94 | | ((self.buf[3] as u32) << 8) 95 | | (self.buf[4] as u32) 96 | } 97 | 98 | /// Get the actual frequency. 99 | /// 100 | /// # Example 101 | /// 102 | /// ``` 103 | /// use stm32wlxx_hal::subghz::RfFreq; 104 | /// 105 | /// assert_eq!(RfFreq::from_raw(0x39300000).freq(), 915_000_000); 106 | /// ``` 107 | pub fn freq(&self) -> u32 { 108 | (32_000_000 * (self.as_bits() as u64) / (1 << 25)) as u32 109 | } 110 | 111 | /// Extracts a slice containing the packet. 112 | /// 113 | /// # Example 114 | /// 115 | /// ``` 116 | /// use stm32wlxx_hal::subghz::RfFreq; 117 | /// 118 | /// assert_eq!(RfFreq::F915.as_slice(), &[0x86, 0x39, 0x30, 0x00, 0x00]); 119 | /// ``` 120 | pub const fn as_slice(&self) -> &[u8] { 121 | &self.buf 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod test { 127 | use super::RfFreq; 128 | 129 | #[test] 130 | fn max() { 131 | assert_eq!(RfFreq::from_raw(u32::MAX).freq(), 4_095_999_999); 132 | } 133 | 134 | #[test] 135 | fn min() { 136 | assert_eq!(RfFreq::from_raw(u32::MIN).freq(), 0); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /hal/src/subghz/rx_timeout_stop.rs: -------------------------------------------------------------------------------- 1 | /// Receiver event which stops the RX timeout timer. 2 | /// 3 | /// Used by [`set_rx_timeout_stop`]. 4 | /// 5 | /// [`set_rx_timeout_stop`]: super::SubGhz::set_rx_timeout_stop 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | #[repr(u8)] 9 | pub enum RxTimeoutStop { 10 | /// Receive timeout stopped on synchronization word detection in generic 11 | /// packet mode or header detection in LoRa packet mode. 12 | Sync = 0b0, 13 | /// Receive timeout stopped on preamble detection. 14 | Preamble = 0b1, 15 | } 16 | 17 | impl From for u8 { 18 | fn from(rx_ts: RxTimeoutStop) -> Self { 19 | rx_ts as u8 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hal/src/subghz/sleep_cfg.rs: -------------------------------------------------------------------------------- 1 | /// Startup configurations when exiting sleep mode. 2 | /// 3 | /// Argument of [`SleepCfg::set_startup`]. 4 | #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 6 | #[repr(u8)] 7 | pub enum Startup { 8 | /// Cold startup when exiting Sleep mode, configuration registers reset. 9 | Cold = 0, 10 | /// Warm startup when exiting Sleep mode, 11 | /// configuration registers kept in retention. 12 | /// 13 | /// **Note:** Only the configuration of the activated modem, 14 | /// before going to sleep mode, is retained. 15 | /// The configuration of the other modes is lost and must be re-configured 16 | /// when exiting sleep mode. 17 | #[default] 18 | Warm = 1, 19 | } 20 | 21 | /// Sleep configuration. 22 | /// 23 | /// Argument of [`set_sleep`]. 24 | /// 25 | /// [`set_sleep`]: super::SubGhz::set_sleep 26 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 27 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 28 | pub struct SleepCfg(u8); 29 | 30 | impl SleepCfg { 31 | /// Create a new `SleepCfg` structure. 32 | /// 33 | /// This is the same as `default`, but in a `const` function. 34 | /// 35 | /// The defaults are a warm startup, with RTC wakeup enabled. 36 | /// 37 | /// # Example 38 | /// 39 | /// ``` 40 | /// use stm32wlxx_hal::subghz::SleepCfg; 41 | /// 42 | /// const SLEEP_CFG: SleepCfg = SleepCfg::new(); 43 | /// assert_eq!(SLEEP_CFG, SleepCfg::default()); 44 | /// # assert_eq!(u8::from(SLEEP_CFG), 0b101); 45 | /// ``` 46 | pub const fn new() -> SleepCfg { 47 | SleepCfg(0) 48 | .set_startup(Startup::Warm) 49 | .set_rtc_wakeup_en(true) 50 | } 51 | 52 | /// Set the startup mode. 53 | /// 54 | /// # Example 55 | /// 56 | /// ``` 57 | /// use stm32wlxx_hal::subghz::{SleepCfg, Startup}; 58 | /// 59 | /// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_startup(Startup::Cold); 60 | /// # assert_eq!(u8::from(SLEEP_CFG), 0b001); 61 | /// # assert_eq!(u8::from(SLEEP_CFG.set_startup(Startup::Warm)), 0b101); 62 | /// ``` 63 | #[must_use = "set_startup returns a modified SleepCfg"] 64 | pub const fn set_startup(mut self, startup: Startup) -> SleepCfg { 65 | if startup as u8 == 1 { 66 | self.0 |= 1 << 2 67 | } else { 68 | self.0 &= !(1 << 2) 69 | } 70 | self 71 | } 72 | 73 | /// Set the RTC wakeup enable. 74 | /// 75 | /// # Example 76 | /// 77 | /// ``` 78 | /// use stm32wlxx_hal::subghz::SleepCfg; 79 | /// 80 | /// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_rtc_wakeup_en(false); 81 | /// # assert_eq!(u8::from(SLEEP_CFG), 0b100); 82 | /// # assert_eq!(u8::from(SLEEP_CFG.set_rtc_wakeup_en(true)), 0b101); 83 | /// ``` 84 | #[must_use = "set_rtc_wakeup_en returns a modified SleepCfg"] 85 | pub const fn set_rtc_wakeup_en(mut self, en: bool) -> SleepCfg { 86 | if en { 87 | self.0 |= 0b1 88 | } else { 89 | self.0 &= !0b1 90 | } 91 | self 92 | } 93 | } 94 | 95 | impl From for u8 { 96 | fn from(sc: SleepCfg) -> Self { 97 | sc.0 98 | } 99 | } 100 | 101 | impl Default for SleepCfg { 102 | fn default() -> Self { 103 | Self::new() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /hal/src/subghz/smps.rs: -------------------------------------------------------------------------------- 1 | /// SMPS maximum drive capability. 2 | /// 3 | /// Argument of [`set_smps_drv`](super::SubGhz::set_smps_drv). 4 | #[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 6 | #[repr(u8)] 7 | pub enum SmpsDrv { 8 | /// 20 mA 9 | Milli20 = 0x0, 10 | /// 40 mA 11 | Milli40 = 0x1, 12 | /// 60 mA 13 | Milli60 = 0x2, 14 | /// 100 mA (default) 15 | #[default] 16 | Milli100 = 0x3, 17 | } 18 | 19 | impl SmpsDrv { 20 | /// Get the SMPS drive value as milliamps. 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// use stm32wlxx_hal::subghz::SmpsDrv; 26 | /// 27 | /// assert_eq!(SmpsDrv::Milli20.as_milliamps(), 20); 28 | /// assert_eq!(SmpsDrv::Milli40.as_milliamps(), 40); 29 | /// assert_eq!(SmpsDrv::Milli60.as_milliamps(), 60); 30 | /// assert_eq!(SmpsDrv::Milli100.as_milliamps(), 100); 31 | /// ``` 32 | pub const fn as_milliamps(&self) -> u8 { 33 | match self { 34 | SmpsDrv::Milli20 => 20, 35 | SmpsDrv::Milli40 => 40, 36 | SmpsDrv::Milli60 => 60, 37 | SmpsDrv::Milli100 => 100, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /hal/src/subghz/standby_clk.rs: -------------------------------------------------------------------------------- 1 | /// Clock in standby mode. 2 | /// 3 | /// Used by [`set_standby`]. 4 | /// 5 | /// [`set_standby`]: super::SubGhz::set_standby 6 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | #[repr(u8)] 9 | pub enum StandbyClk { 10 | /// RC 13 MHz used in standby mode. 11 | Rc = 0b0, 12 | /// HSE32 used in standby mode. 13 | Hse = 0b1, 14 | } 15 | 16 | impl From for u8 { 17 | fn from(sc: StandbyClk) -> Self { 18 | sc as u8 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hal/src/subghz/stats.rs: -------------------------------------------------------------------------------- 1 | use super::Status; 2 | 3 | typestate!(LoRaStats, "LoRa stats"); 4 | typestate!(FskStats, "FSK stats"); 5 | 6 | /// Packet statistics. 7 | /// 8 | /// Returned by [`fsk_stats`] and [`lora_stats`]. 9 | /// 10 | /// [`fsk_stats`]: super::SubGhz::fsk_stats 11 | /// [`lora_stats`]: super::SubGhz::lora_stats 12 | #[derive(Eq, PartialEq, Clone, Copy)] 13 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 14 | pub struct Stats { 15 | status: Status, 16 | pkt_rx: u16, 17 | pkt_crc: u16, 18 | pkt_len_or_hdr_err: u16, 19 | ty: ModType, 20 | } 21 | 22 | impl Stats { 23 | const fn from_buf(buf: [u8; 7], ty: ModType) -> Stats { 24 | Stats { 25 | status: Status::from_raw(buf[0]), 26 | pkt_rx: u16::from_be_bytes([buf[1], buf[2]]), 27 | pkt_crc: u16::from_be_bytes([buf[3], buf[4]]), 28 | pkt_len_or_hdr_err: u16::from_be_bytes([buf[5], buf[6]]), 29 | ty, 30 | } 31 | } 32 | 33 | /// Get the radio status returned with the packet statistics. 34 | /// 35 | /// # Example 36 | /// 37 | /// ``` 38 | /// use stm32wlxx_hal::subghz::{CmdStatus, FskStats, Stats, StatusMode}; 39 | /// 40 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; 41 | /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); 42 | /// assert_eq!(stats.status().mode(), Ok(StatusMode::Rx)); 43 | /// assert_eq!(stats.status().cmd(), Ok(CmdStatus::Available)); 44 | /// ``` 45 | pub const fn status(&self) -> Status { 46 | self.status 47 | } 48 | 49 | /// Number of packets received. 50 | /// 51 | /// # Example 52 | /// 53 | /// ``` 54 | /// use stm32wlxx_hal::subghz::{FskStats, Stats}; 55 | /// 56 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 3, 0, 0, 0, 0]; 57 | /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); 58 | /// assert_eq!(stats.pkt_rx(), 3); 59 | /// ``` 60 | pub const fn pkt_rx(&self) -> u16 { 61 | self.pkt_rx 62 | } 63 | 64 | /// Number of packets received with a payload CRC error 65 | /// 66 | /// # Example 67 | /// 68 | /// ``` 69 | /// use stm32wlxx_hal::subghz::{LoRaStats, Stats}; 70 | /// 71 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 1, 0, 0]; 72 | /// let stats: Stats = Stats::from_raw_lora(example_data_from_radio); 73 | /// assert_eq!(stats.pkt_crc(), 1); 74 | /// ``` 75 | pub const fn pkt_crc(&self) -> u16 { 76 | self.pkt_crc 77 | } 78 | } 79 | 80 | impl Stats { 81 | /// Create a new FSK packet statistics structure from a raw buffer. 82 | /// 83 | /// # Example 84 | /// 85 | /// ``` 86 | /// use stm32wlxx_hal::subghz::{FskStats, Stats}; 87 | /// 88 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; 89 | /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); 90 | /// ``` 91 | pub const fn from_raw_fsk(buf: [u8; 7]) -> Stats { 92 | Self::from_buf(buf, FskStats::new()) 93 | } 94 | 95 | /// Number of packets received with a payload length error. 96 | /// 97 | /// # Example 98 | /// 99 | /// ``` 100 | /// use stm32wlxx_hal::subghz::{FskStats, Stats}; 101 | /// 102 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1]; 103 | /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); 104 | /// assert_eq!(stats.pkt_len_err(), 1); 105 | /// ``` 106 | pub const fn pkt_len_err(&self) -> u16 { 107 | self.pkt_len_or_hdr_err 108 | } 109 | } 110 | 111 | impl Stats { 112 | /// Create a new LoRa packet statistics structure from a raw buffer. 113 | /// 114 | /// # Example 115 | /// 116 | /// ``` 117 | /// use stm32wlxx_hal::subghz::{LoRaStats, Stats}; 118 | /// 119 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; 120 | /// let stats: Stats = Stats::from_raw_lora(example_data_from_radio); 121 | /// ``` 122 | pub const fn from_raw_lora(buf: [u8; 7]) -> Stats { 123 | Self::from_buf(buf, LoRaStats::new()) 124 | } 125 | 126 | /// Number of packets received with a header CRC error. 127 | /// 128 | /// # Example 129 | /// 130 | /// ``` 131 | /// use stm32wlxx_hal::subghz::{LoRaStats, Stats}; 132 | /// 133 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1]; 134 | /// let stats: Stats = Stats::from_raw_lora(example_data_from_radio); 135 | /// assert_eq!(stats.pkt_hdr_err(), 1); 136 | /// ``` 137 | pub const fn pkt_hdr_err(&self) -> u16 { 138 | self.pkt_len_or_hdr_err 139 | } 140 | } 141 | 142 | impl core::fmt::Debug for Stats { 143 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 144 | f.debug_struct("Stats") 145 | .field("status", &self.status()) 146 | .field("pkt_rx", &self.pkt_rx()) 147 | .field("pkt_crc", &self.pkt_crc()) 148 | .field("pkt_len_err", &self.pkt_len_err()) 149 | .finish() 150 | } 151 | } 152 | 153 | #[cfg(test)] 154 | mod test { 155 | use super::super::{CmdStatus, LoRaStats, Stats, StatusMode}; 156 | 157 | #[test] 158 | fn mixed() { 159 | let example_data_from_radio: [u8; 7] = [0x54, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; 160 | let stats: Stats = Stats::from_raw_lora(example_data_from_radio); 161 | assert_eq!(stats.status().mode(), Ok(StatusMode::Rx)); 162 | assert_eq!(stats.status().cmd(), Ok(CmdStatus::Available)); 163 | assert_eq!(stats.pkt_rx(), 0x0102); 164 | assert_eq!(stats.pkt_crc(), 0x0304); 165 | assert_eq!(stats.pkt_hdr_err(), 0x0506); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /hal/src/subghz/status.rs: -------------------------------------------------------------------------------- 1 | /// sub-GHz radio operating mode. 2 | /// 3 | /// See `Get_Status` under section 5.8.5 "Communication status information commands" 4 | /// in the reference manual. 5 | /// 6 | /// This is returned by [`Status::mode`]. 7 | #[repr(u8)] 8 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 9 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 10 | pub enum StatusMode { 11 | /// Standby mode with RC 13MHz. 12 | StandbyRc = 0x2, 13 | /// Standby mode with HSE32. 14 | StandbyHse = 0x3, 15 | /// Frequency Synthesis mode. 16 | Fs = 0x4, 17 | /// Receive mode. 18 | Rx = 0x5, 19 | /// Transmit mode. 20 | Tx = 0x6, 21 | } 22 | 23 | impl StatusMode { 24 | /// Create a new `StatusMode` from bits. 25 | /// 26 | /// # Example 27 | /// 28 | /// ``` 29 | /// use stm32wlxx_hal::subghz::StatusMode; 30 | /// 31 | /// assert_eq!(StatusMode::from_raw(0x2), Ok(StatusMode::StandbyRc)); 32 | /// assert_eq!(StatusMode::from_raw(0x3), Ok(StatusMode::StandbyHse)); 33 | /// assert_eq!(StatusMode::from_raw(0x4), Ok(StatusMode::Fs)); 34 | /// assert_eq!(StatusMode::from_raw(0x5), Ok(StatusMode::Rx)); 35 | /// assert_eq!(StatusMode::from_raw(0x6), Ok(StatusMode::Tx)); 36 | /// // Other values are reserved 37 | /// assert_eq!(StatusMode::from_raw(0), Err(0)); 38 | /// ``` 39 | pub const fn from_raw(bits: u8) -> Result { 40 | match bits { 41 | 0x2 => Ok(StatusMode::StandbyRc), 42 | 0x3 => Ok(StatusMode::StandbyHse), 43 | 0x4 => Ok(StatusMode::Fs), 44 | 0x5 => Ok(StatusMode::Rx), 45 | 0x6 => Ok(StatusMode::Tx), 46 | _ => Err(bits), 47 | } 48 | } 49 | } 50 | 51 | /// Command status. 52 | /// 53 | /// See `Get_Status` under section 5.8.5 "Communication status information commands" 54 | /// in the reference manual. 55 | /// 56 | /// This is returned by [`Status::cmd`]. 57 | #[repr(u8)] 58 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 59 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 60 | pub enum CmdStatus { 61 | /// Data available to host. 62 | /// 63 | /// Packet received successfully and data can be retrieved. 64 | Available = 0x2, 65 | /// Command time out. 66 | /// 67 | /// Command took too long to complete triggering a sub-GHz radio watchdog 68 | /// timeout. 69 | Timeout = 0x3, 70 | /// Command processing error. 71 | /// 72 | /// Invalid opcode or incorrect number of parameters. 73 | ProcessingError = 0x4, 74 | /// Command execution failure. 75 | /// 76 | /// Command successfully received but cannot be executed at this time, 77 | /// requested operating mode cannot be entered or requested data cannot be 78 | /// sent. 79 | ExecutionFailure = 0x5, 80 | /// Transmit command completed. 81 | /// 82 | /// Current packet transmission completed. 83 | Complete = 0x6, 84 | } 85 | 86 | impl CmdStatus { 87 | /// Create a new `CmdStatus` from bits. 88 | /// 89 | /// # Example 90 | /// 91 | /// ``` 92 | /// use stm32wlxx_hal::subghz::CmdStatus; 93 | /// 94 | /// assert_eq!(CmdStatus::from_raw(0x2), Ok(CmdStatus::Available)); 95 | /// assert_eq!(CmdStatus::from_raw(0x3), Ok(CmdStatus::Timeout)); 96 | /// assert_eq!(CmdStatus::from_raw(0x4), Ok(CmdStatus::ProcessingError)); 97 | /// assert_eq!(CmdStatus::from_raw(0x5), Ok(CmdStatus::ExecutionFailure)); 98 | /// assert_eq!(CmdStatus::from_raw(0x6), Ok(CmdStatus::Complete)); 99 | /// // Other values are reserved 100 | /// assert_eq!(CmdStatus::from_raw(0), Err(0)); 101 | /// ``` 102 | pub const fn from_raw(bits: u8) -> Result { 103 | match bits { 104 | 0x2 => Ok(CmdStatus::Available), 105 | 0x3 => Ok(CmdStatus::Timeout), 106 | 0x4 => Ok(CmdStatus::ProcessingError), 107 | 0x5 => Ok(CmdStatus::ExecutionFailure), 108 | 0x6 => Ok(CmdStatus::Complete), 109 | _ => Err(bits), 110 | } 111 | } 112 | } 113 | 114 | /// Radio status. 115 | /// 116 | /// This is returned by [`status`]. 117 | /// 118 | /// [`status`]: super::SubGhz::status 119 | #[derive(PartialEq, Eq, Clone, Copy)] 120 | pub struct Status(u8); 121 | 122 | impl From for Status { 123 | fn from(x: u8) -> Self { 124 | Status(x) 125 | } 126 | } 127 | impl From for u8 { 128 | fn from(x: Status) -> Self { 129 | x.0 130 | } 131 | } 132 | 133 | impl Status { 134 | /// Create a new `Status` from a raw `u8` value. 135 | /// 136 | /// This is the same as `Status::from(u8)`, but in a `const` function. 137 | /// 138 | /// # Example 139 | /// 140 | /// ``` 141 | /// use stm32wlxx_hal::subghz::{CmdStatus, Status, StatusMode}; 142 | /// 143 | /// const STATUS: Status = Status::from_raw(0x54_u8); 144 | /// assert_eq!(STATUS.mode(), Ok(StatusMode::Rx)); 145 | /// assert_eq!(STATUS.cmd(), Ok(CmdStatus::Available)); 146 | /// ``` 147 | pub const fn from_raw(value: u8) -> Status { 148 | Status(value) 149 | } 150 | 151 | /// sub-GHz radio operating mode. 152 | /// 153 | /// # Example 154 | /// 155 | /// ``` 156 | /// use stm32wlxx_hal::subghz::{Status, StatusMode}; 157 | /// 158 | /// let status: Status = 0xACu8.into(); 159 | /// assert_eq!(status.mode(), Ok(StatusMode::StandbyRc)); 160 | /// ``` 161 | pub const fn mode(&self) -> Result { 162 | StatusMode::from_raw((self.0 >> 4) & 0b111) 163 | } 164 | 165 | /// Command status. 166 | /// 167 | /// This method frequently returns reserved values such as `Err(1)`. 168 | /// ST support has confirmed that this is normal and should be ignored. 169 | /// 170 | /// # Example 171 | /// 172 | /// ``` 173 | /// use stm32wlxx_hal::subghz::{CmdStatus, Status}; 174 | /// 175 | /// let status: Status = 0xACu8.into(); 176 | /// assert_eq!(status.cmd(), Ok(CmdStatus::Complete)); 177 | /// ``` 178 | pub const fn cmd(&self) -> Result { 179 | CmdStatus::from_raw((self.0 >> 1) & 0b111) 180 | } 181 | } 182 | 183 | impl core::fmt::Debug for Status { 184 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 185 | f.debug_struct("Status") 186 | .field("mode", &self.mode()) 187 | .field("cmd", &self.cmd()) 188 | .finish() 189 | } 190 | } 191 | 192 | #[cfg(feature = "defmt")] 193 | impl defmt::Format for Status { 194 | fn format(&self, fmt: defmt::Formatter) { 195 | defmt::write!( 196 | fmt, 197 | "Status {{ mode: {}, cmd: {} }}", 198 | self.mode(), 199 | self.cmd() 200 | ) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /hal/src/subghz/tcxo_mode.rs: -------------------------------------------------------------------------------- 1 | use super::Timeout; 2 | 3 | /// TCXO trim. 4 | /// 5 | /// **Note:** To use VDDTCXO, the VDDRF supply must be at 6 | /// least + 200 mV higher than the selected `TcxoTrim` voltage level. 7 | /// 8 | /// Used by [`TcxoMode`]. 9 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 11 | #[repr(u8)] 12 | pub enum TcxoTrim { 13 | /// 1.6V 14 | Volts1pt6 = 0x0, 15 | /// 1.7V 16 | Volts1pt7 = 0x1, 17 | /// 1.8V 18 | Volts1pt8 = 0x2, 19 | /// 2.2V 20 | Volts2pt2 = 0x3, 21 | /// 2.4V 22 | Volts2pt4 = 0x4, 23 | /// 2.7V 24 | Volts2pt7 = 0x5, 25 | /// 3.0V 26 | Volts3pt0 = 0x6, 27 | /// 3.3V 28 | Volts3pt3 = 0x7, 29 | } 30 | 31 | impl core::fmt::Display for TcxoTrim { 32 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 33 | match self { 34 | TcxoTrim::Volts1pt6 => write!(f, "1.6V"), 35 | TcxoTrim::Volts1pt7 => write!(f, "1.7V"), 36 | TcxoTrim::Volts1pt8 => write!(f, "1.8V"), 37 | TcxoTrim::Volts2pt2 => write!(f, "2.2V"), 38 | TcxoTrim::Volts2pt4 => write!(f, "2.4V"), 39 | TcxoTrim::Volts2pt7 => write!(f, "2.7V"), 40 | TcxoTrim::Volts3pt0 => write!(f, "3.0V"), 41 | TcxoTrim::Volts3pt3 => write!(f, "3.3V"), 42 | } 43 | } 44 | } 45 | 46 | impl TcxoTrim { 47 | /// Get the value of the TCXO trim in millivolts. 48 | /// 49 | /// # Example 50 | /// 51 | /// ``` 52 | /// use stm32wlxx_hal::subghz::TcxoTrim; 53 | /// 54 | /// assert_eq!(TcxoTrim::Volts1pt6.as_millivolts(), 1600); 55 | /// assert_eq!(TcxoTrim::Volts1pt7.as_millivolts(), 1700); 56 | /// assert_eq!(TcxoTrim::Volts1pt8.as_millivolts(), 1800); 57 | /// assert_eq!(TcxoTrim::Volts2pt2.as_millivolts(), 2200); 58 | /// assert_eq!(TcxoTrim::Volts2pt4.as_millivolts(), 2400); 59 | /// assert_eq!(TcxoTrim::Volts2pt7.as_millivolts(), 2700); 60 | /// assert_eq!(TcxoTrim::Volts3pt0.as_millivolts(), 3000); 61 | /// assert_eq!(TcxoTrim::Volts3pt3.as_millivolts(), 3300); 62 | /// ``` 63 | pub const fn as_millivolts(&self) -> u16 { 64 | match self { 65 | TcxoTrim::Volts1pt6 => 1600, 66 | TcxoTrim::Volts1pt7 => 1700, 67 | TcxoTrim::Volts1pt8 => 1800, 68 | TcxoTrim::Volts2pt2 => 2200, 69 | TcxoTrim::Volts2pt4 => 2400, 70 | TcxoTrim::Volts2pt7 => 2700, 71 | TcxoTrim::Volts3pt0 => 3000, 72 | TcxoTrim::Volts3pt3 => 3300, 73 | } 74 | } 75 | } 76 | 77 | /// TCXO trim and HSE32 ready timeout. 78 | /// 79 | /// Argument of [`set_tcxo_mode`]. 80 | /// 81 | /// [`set_tcxo_mode`]: super::SubGhz::set_tcxo_mode 82 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 83 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 84 | pub struct TcxoMode { 85 | buf: [u8; 5], 86 | } 87 | 88 | impl TcxoMode { 89 | /// Create a new `TcxoMode` struct. 90 | /// 91 | /// This is the same as `default`, but in a `const` function. 92 | /// 93 | /// # Example 94 | /// 95 | /// ``` 96 | /// use stm32wlxx_hal::subghz::TcxoMode; 97 | /// 98 | /// const TCXO_MODE: TcxoMode = TcxoMode::new(); 99 | /// ``` 100 | pub const fn new() -> TcxoMode { 101 | TcxoMode { 102 | buf: [super::OpCode::SetTcxoMode as u8, 0x00, 0x00, 0x00, 0x00], 103 | } 104 | } 105 | 106 | /// Set the TCXO trim. 107 | /// 108 | /// **Note:** To use VDDTCXO, the VDDRF supply must be 109 | /// at least + 200 mV higher than the selected `TcxoTrim` voltage level. 110 | /// 111 | /// # Example 112 | /// 113 | /// ``` 114 | /// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim}; 115 | /// 116 | /// const TCXO_MODE: TcxoMode = TcxoMode::new().set_tcxo_trim(TcxoTrim::Volts1pt6); 117 | /// # assert_eq!(TCXO_MODE.as_slice()[1], 0x00); 118 | /// ``` 119 | #[must_use = "set_tcxo_trim returns a modified TcxoMode"] 120 | pub const fn set_tcxo_trim(mut self, tcxo_trim: TcxoTrim) -> TcxoMode { 121 | self.buf[1] = tcxo_trim as u8; 122 | self 123 | } 124 | 125 | /// Set the ready timeout duration. 126 | /// 127 | /// # Example 128 | /// 129 | /// ``` 130 | /// use core::time::Duration; 131 | /// use stm32wlxx_hal::subghz::{TcxoMode, Timeout}; 132 | /// 133 | /// // 15.625 ms timeout 134 | /// const TIMEOUT: Timeout = Timeout::from_duration_sat(Duration::from_millis(15_625)); 135 | /// const TCXO_MODE: TcxoMode = TcxoMode::new().set_timeout(TIMEOUT); 136 | /// # assert_eq!(TCXO_MODE.as_slice()[2], 0x0F); 137 | /// # assert_eq!(TCXO_MODE.as_slice()[3], 0x42); 138 | /// # assert_eq!(TCXO_MODE.as_slice()[4], 0x40); 139 | /// ``` 140 | #[must_use = "set_timeout returns a modified TcxoMode"] 141 | pub const fn set_timeout(mut self, timeout: Timeout) -> TcxoMode { 142 | let timeout_bits: u32 = timeout.into_bits(); 143 | self.buf[2] = ((timeout_bits >> 16) & 0xFF) as u8; 144 | self.buf[3] = ((timeout_bits >> 8) & 0xFF) as u8; 145 | self.buf[4] = (timeout_bits & 0xFF) as u8; 146 | self 147 | } 148 | 149 | /// Extracts a slice containing the packet. 150 | /// 151 | /// # Example 152 | /// 153 | /// ``` 154 | /// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim, Timeout}; 155 | /// 156 | /// const TCXO_MODE: TcxoMode = TcxoMode::new() 157 | /// .set_tcxo_trim(TcxoTrim::Volts1pt7) 158 | /// .set_timeout(Timeout::from_raw(0x123456)); 159 | /// assert_eq!(TCXO_MODE.as_slice(), &[0x97, 0x1, 0x12, 0x34, 0x56]); 160 | /// ``` 161 | pub const fn as_slice(&self) -> &[u8] { 162 | &self.buf 163 | } 164 | } 165 | 166 | impl Default for TcxoMode { 167 | fn default() -> Self { 168 | Self::new() 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /hal/src/subghz/tx_params.rs: -------------------------------------------------------------------------------- 1 | /// Power amplifier ramp time for FSK, MSK, and LoRa modulation. 2 | /// 3 | /// Argument of [`set_ramp_time`][`super::TxParams::set_ramp_time`]. 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 6 | #[repr(u8)] 7 | pub enum RampTime { 8 | /// 10µs 9 | Micros10 = 0x00, 10 | /// 20µs 11 | Micros20 = 0x01, 12 | /// 40µs 13 | Micros40 = 0x02, 14 | /// 80µs 15 | Micros80 = 0x03, 16 | /// 200µs 17 | Micros200 = 0x04, 18 | /// 800µs 19 | Micros800 = 0x05, 20 | /// 1.7ms 21 | Micros1700 = 0x06, 22 | /// 3.4ms 23 | Micros3400 = 0x07, 24 | } 25 | 26 | impl From for u8 { 27 | fn from(rt: RampTime) -> Self { 28 | rt as u8 29 | } 30 | } 31 | 32 | impl From for core::time::Duration { 33 | fn from(rt: RampTime) -> Self { 34 | match rt { 35 | RampTime::Micros10 => core::time::Duration::from_micros(10), 36 | RampTime::Micros20 => core::time::Duration::from_micros(20), 37 | RampTime::Micros40 => core::time::Duration::from_micros(40), 38 | RampTime::Micros80 => core::time::Duration::from_micros(80), 39 | RampTime::Micros200 => core::time::Duration::from_micros(200), 40 | RampTime::Micros800 => core::time::Duration::from_micros(800), 41 | RampTime::Micros1700 => core::time::Duration::from_micros(1700), 42 | RampTime::Micros3400 => core::time::Duration::from_micros(3400), 43 | } 44 | } 45 | } 46 | 47 | #[cfg(feature = "embedded-time")] 48 | impl From for embedded_time::duration::Microseconds { 49 | fn from(rt: RampTime) -> Self { 50 | match rt { 51 | RampTime::Micros10 => embedded_time::duration::Microseconds(10), 52 | RampTime::Micros20 => embedded_time::duration::Microseconds(20), 53 | RampTime::Micros40 => embedded_time::duration::Microseconds(40), 54 | RampTime::Micros80 => embedded_time::duration::Microseconds(80), 55 | RampTime::Micros200 => embedded_time::duration::Microseconds(200), 56 | RampTime::Micros800 => embedded_time::duration::Microseconds(800), 57 | RampTime::Micros1700 => embedded_time::duration::Microseconds(1700), 58 | RampTime::Micros3400 => embedded_time::duration::Microseconds(3400), 59 | } 60 | } 61 | } 62 | /// Transmit parameters, output power and power amplifier ramp up time. 63 | /// 64 | /// Argument of [`set_tx_params`][`super::SubGhz::set_tx_params`]. 65 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 66 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 67 | pub struct TxParams { 68 | buf: [u8; 3], 69 | } 70 | 71 | impl TxParams { 72 | /// Optimal power setting for +15dBm output power with the low-power PA. 73 | /// 74 | /// This must be used with [`PaConfig::LP_15`](super::PaConfig::LP_15). 75 | pub const LP_15: TxParams = TxParams::new().set_power(0x0E); 76 | 77 | /// Optimal power setting for +14dBm output power with the low-power PA. 78 | /// 79 | /// This must be used with [`PaConfig::LP_14`](super::PaConfig::LP_14). 80 | pub const LP_14: TxParams = TxParams::new().set_power(0x0E); 81 | 82 | /// Optimal power setting for +10dBm output power with the low-power PA. 83 | /// 84 | /// This must be used with [`PaConfig::LP_10`](super::PaConfig::LP_10). 85 | pub const LP_10: TxParams = TxParams::new().set_power(0x0D); 86 | 87 | /// Optimal power setting for the high-power PA. 88 | /// 89 | /// This must be used with one of: 90 | /// 91 | /// * [`PaConfig::HP_22`](super::PaConfig::HP_22) 92 | /// * [`PaConfig::HP_20`](super::PaConfig::HP_20) 93 | /// * [`PaConfig::HP_17`](super::PaConfig::HP_17) 94 | /// * [`PaConfig::HP_14`](super::PaConfig::HP_14) 95 | pub const HP: TxParams = TxParams::new().set_power(0x16); 96 | 97 | /// Create a new `TxParams` struct. 98 | /// 99 | /// This is the same as `default`, but in a `const` function. 100 | /// 101 | /// # Example 102 | /// 103 | /// ``` 104 | /// use stm32wlxx_hal::subghz::TxParams; 105 | /// 106 | /// const TX_PARAMS: TxParams = TxParams::new(); 107 | /// assert_eq!(TX_PARAMS, TxParams::default()); 108 | /// ``` 109 | pub const fn new() -> TxParams { 110 | TxParams { 111 | buf: [super::OpCode::SetTxParams as u8, 0x00, 0x00], 112 | } 113 | } 114 | 115 | /// Set the output power. 116 | /// 117 | /// For low power selected in [`set_pa_config`]: 118 | /// 119 | /// * 0x0E: +14 dB 120 | /// * ... 121 | /// * 0x00: 0 dB 122 | /// * ... 123 | /// * 0xEF: -17 dB 124 | /// * Others: reserved 125 | /// 126 | /// For high power selected in [`set_pa_config`]: 127 | /// 128 | /// * 0x16: +22 dB 129 | /// * ... 130 | /// * 0x00: 0 dB 131 | /// * ... 132 | /// * 0xF7: -9 dB 133 | /// * Others: reserved 134 | /// 135 | /// # Example 136 | /// 137 | /// Set the output power to 0 dB. 138 | /// 139 | /// ``` 140 | /// use stm32wlxx_hal::subghz::{RampTime, TxParams}; 141 | /// 142 | /// const TX_PARAMS: TxParams = TxParams::new().set_power(0x00); 143 | /// # assert_eq!(TX_PARAMS.as_slice()[1], 0x00); 144 | /// ``` 145 | /// 146 | /// [`set_pa_config`]: super::SubGhz::set_pa_config 147 | #[must_use = "set_power returns a modified TxParams"] 148 | pub const fn set_power(mut self, power: u8) -> TxParams { 149 | self.buf[1] = power; 150 | self 151 | } 152 | 153 | /// Set the Power amplifier ramp time for FSK, MSK, and LoRa modulation. 154 | /// 155 | /// # Example 156 | /// 157 | /// Set the ramp time to 200 microseconds. 158 | /// 159 | /// ``` 160 | /// use stm32wlxx_hal::subghz::{RampTime, TxParams}; 161 | /// 162 | /// const TX_PARAMS: TxParams = TxParams::new().set_ramp_time(RampTime::Micros200); 163 | /// # assert_eq!(TX_PARAMS.as_slice()[2], 0x04); 164 | /// ``` 165 | #[must_use = "set_ramp_time returns a modified TxParams"] 166 | pub const fn set_ramp_time(mut self, rt: RampTime) -> TxParams { 167 | self.buf[2] = rt as u8; 168 | self 169 | } 170 | 171 | /// Extracts a slice containing the packet. 172 | /// 173 | /// # Example 174 | /// 175 | /// ``` 176 | /// use stm32wlxx_hal::subghz::{RampTime, TxParams}; 177 | /// 178 | /// const TX_PARAMS: TxParams = TxParams::new() 179 | /// .set_ramp_time(RampTime::Micros80) 180 | /// .set_power(0x0E); 181 | /// assert_eq!(TX_PARAMS.as_slice(), &[0x8E, 0x0E, 0x03]); 182 | /// ``` 183 | pub const fn as_slice(&self) -> &[u8] { 184 | &self.buf 185 | } 186 | } 187 | 188 | impl Default for TxParams { 189 | fn default() -> Self { 190 | Self::new() 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /hal/src/subghz/value_error.rs: -------------------------------------------------------------------------------- 1 | /// Error for a value that is out-of-bounds. 2 | /// 3 | /// Used by [`Timeout::from_duration`]. 4 | /// 5 | /// [`Timeout::from_duration`]: super::Timeout::from_duration 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | pub struct ValueError { 9 | value: T, 10 | limit: T, 11 | over: bool, 12 | } 13 | 14 | impl ValueError { 15 | /// Create a new `ValueError` for a value that exceeded an upper bound. 16 | /// 17 | /// Unfortunately panic is not available in `const fn`, so there are no 18 | /// guarantees on the value being greater than the limit. 19 | /// 20 | /// # Example 21 | /// 22 | /// ``` 23 | /// use stm32wlxx_hal::subghz::ValueError; 24 | /// 25 | /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); 26 | /// assert!(ERROR.over()); 27 | /// assert!(!ERROR.under()); 28 | /// ``` 29 | pub const fn too_high(value: T, limit: T) -> ValueError { 30 | ValueError { 31 | value, 32 | limit, 33 | over: true, 34 | } 35 | } 36 | 37 | /// Create a new `ValueError` for a value that exceeded a lower bound. 38 | /// 39 | /// Unfortunately panic is not available in `const fn`, so there are no 40 | /// guarantees on the value being less than the limit. 41 | /// 42 | /// # Example 43 | /// 44 | /// ``` 45 | /// use stm32wlxx_hal::subghz::ValueError; 46 | /// 47 | /// const ERROR: ValueError = ValueError::too_low(200u8, 201u8); 48 | /// assert!(ERROR.under()); 49 | /// assert!(!ERROR.over()); 50 | /// ``` 51 | pub const fn too_low(value: T, limit: T) -> ValueError { 52 | ValueError { 53 | value, 54 | limit, 55 | over: false, 56 | } 57 | } 58 | 59 | /// Get the value that caused the error. 60 | /// 61 | /// # Example 62 | /// 63 | /// ``` 64 | /// use stm32wlxx_hal::subghz::ValueError; 65 | /// 66 | /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); 67 | /// assert_eq!(ERROR.value(), &101u8); 68 | /// ``` 69 | pub const fn value(&self) -> &T { 70 | &self.value 71 | } 72 | 73 | /// Get the limit for the value. 74 | /// 75 | /// # Example 76 | /// 77 | /// ``` 78 | /// use stm32wlxx_hal::subghz::ValueError; 79 | /// 80 | /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); 81 | /// assert_eq!(ERROR.limit(), &100u8); 82 | /// ``` 83 | pub const fn limit(&self) -> &T { 84 | &self.limit 85 | } 86 | 87 | /// Returns `true` if the value was over the limit. 88 | /// 89 | /// # Example 90 | /// 91 | /// ``` 92 | /// use stm32wlxx_hal::subghz::ValueError; 93 | /// 94 | /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); 95 | /// assert!(ERROR.over()); 96 | /// assert!(!ERROR.under()); 97 | /// ``` 98 | pub const fn over(&self) -> bool { 99 | self.over 100 | } 101 | 102 | /// Returns `true` if the value was under the limit. 103 | /// 104 | /// # Example 105 | /// 106 | /// ``` 107 | /// use stm32wlxx_hal::subghz::ValueError; 108 | /// 109 | /// const ERROR: ValueError = ValueError::too_low(200u8, 201u8); 110 | /// assert!(ERROR.under()); 111 | /// assert!(!ERROR.over()); 112 | /// ``` 113 | pub const fn under(&self) -> bool { 114 | !self.over 115 | } 116 | } 117 | 118 | impl core::fmt::Display for ValueError 119 | where 120 | T: core::fmt::Display, 121 | { 122 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 123 | if self.over { 124 | write!(f, "Value is too high {} > {}", self.value, self.limit) 125 | } else { 126 | write!(f, "Value is too low {} < {}", self.value, self.limit) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /hal/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous utilities 2 | use crate::pac; 3 | use cortex_m::{delay::Delay, peripheral::syst::SystClkSource}; 4 | 5 | /// Create a new [`cortex_m::delay::Delay`] from the current CPU systick 6 | /// frequency. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```no_run 11 | /// use stm32wlxx_hal::{pac, util::new_delay}; 12 | /// 13 | /// let dp = pac::Peripherals::take().unwrap(); 14 | /// let cp = pac::CorePeripherals::take().unwrap(); 15 | /// let delay = new_delay(cp.SYST, &dp.RCC); 16 | /// ``` 17 | #[inline] 18 | pub fn new_delay(syst: pac::SYST, rcc: &pac::RCC) -> Delay { 19 | Delay::new( 20 | syst, 21 | // Delay constructor will set SystClkSource::Core 22 | crate::rcc::cpu_systick_hz(rcc, SystClkSource::Core), 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /lora-e5-bsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lora-e5-bsp" 3 | description = "Board support package for the seeed LoRa-E5 development kit" 4 | readme = "README.md" 5 | keywords = ["arm", "cortex-m", "stm32", "bsp", "seeed"] 6 | categories = ["embedded", "hardware-support", "no-std"] 7 | 8 | authors.workspace = true 9 | edition.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | version.workspace = true 14 | 15 | [features] 16 | chrono = ["stm32wlxx-hal/chrono"] 17 | defmt = ["stm32wlxx-hal/defmt", "dep:defmt"] 18 | embedded-time = ["stm32wlxx-hal/embedded-time"] 19 | rt = ["stm32wlxx-hal/rt"] 20 | 21 | [dependencies.stm32wlxx-hal] 22 | version = "=0.6.1" 23 | path = "../hal" 24 | features = ["stm32wle5"] 25 | 26 | [dependencies.defmt] 27 | version = "1" 28 | optional = true 29 | 30 | [package.metadata.docs.rs] 31 | all-features = false 32 | features = ["rt", "embedded-time", "chrono"] 33 | rustdoc-args = ["--cfg", "docsrs"] 34 | -------------------------------------------------------------------------------- /lora-e5-bsp/README.md: -------------------------------------------------------------------------------- 1 | # LoRa-E5 Board Support Package 2 | 3 | Board support for the seeed LoRa-E5 development kit. 4 | 5 | This crate extends the [stm32wlxx-hal] with board specific hardware, see that crate for more information. 6 | 7 | ## Usage 8 | 9 | ```toml 10 | [dependencies.lora-e5-bsp] 11 | version = "0.6.1" 12 | features = [ 13 | # optional: use the cortex-m-rt interrupt interface 14 | "rt", 15 | # optional: use defmt 16 | "defmt", 17 | # optional: enable conversions with embedded-time types 18 | "embedded-time", 19 | # optional: use the real time clock (RTC) 20 | "chrono", 21 | ] 22 | ``` 23 | 24 | ## Flashing 25 | 26 | This board is a pain to get working for the first time because the flash protection bits are set. 27 | 28 | Check these resources to unlock the board for programming: 29 | 30 | * [How to program a LoRa-E5](https://forum.seeedstudio.com/t/how-to-program-a-lora-e5/257491) 31 | * [seeed-lora/LoRa-E5-LoRaWAN-End-Node](https://github.com/seeed-lora/LoRa-E5-LoRaWAN-End-Node#getting-started) 32 | * [stm32wl-unlock](https://github.com/newAM/stm32wl-unlock/) 33 | 34 | To flash this board with various rust utilities such as `probe-run`, `cargo-embed`, and `cargo-flash` remove the `--connected-under-reset` flag. This flag is required for the NUCLEO board, but will cause timeout errors with the LoRa-E5 development board. 35 | 36 | ⚠️ You must use recent versions of `probe-rs` based tools to avoid bugs with the STM32WL ⚠️ 37 | 38 | * `cargo-embed` >=0.12.0 39 | * `cargo-flash` >=0.12.0 40 | * `probe-run` >=0.3.1 41 | 42 | [stm32wlxx-hal]: https://github.com/stm32-rs/stm32wlxx-hal 43 | -------------------------------------------------------------------------------- /lora-e5-bsp/src/led.rs: -------------------------------------------------------------------------------- 1 | //! LEDs 2 | 3 | use stm32wlxx_hal as hal; 4 | 5 | use core::ops::Not; 6 | use hal::{ 7 | cortex_m::interrupt::CriticalSection, 8 | embedded_hal::digital::v2::OutputPin, 9 | gpio::{self, Output, OutputArgs, pins}, 10 | }; 11 | 12 | const LED_ARGS: OutputArgs = OutputArgs { 13 | speed: gpio::Speed::Fast, 14 | level: gpio::PinState::High, 15 | ot: gpio::OutputType::PushPull, 16 | pull: gpio::Pull::None, 17 | }; 18 | 19 | /// D5 LED. 20 | #[derive(Debug)] 21 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 22 | pub struct D5 { 23 | gpio: Output, 24 | } 25 | 26 | impl D5 { 27 | /// Create a new D5 LED. 28 | /// 29 | /// # Example 30 | /// 31 | /// ```no_run 32 | /// use lora_e5_bsp::{ 33 | /// hal::{cortex_m, gpio::PortB, pac}, 34 | /// led, 35 | /// }; 36 | /// 37 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 38 | /// 39 | /// let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 40 | /// let mut d5 = cortex_m::interrupt::free(|cs| led::D5::new(gpiob.b5, cs)); 41 | /// d5.set_on(); 42 | /// ``` 43 | pub fn new(b5: pins::B5, cs: &CriticalSection) -> Self { 44 | Self { 45 | gpio: Output::new(b5, &LED_ARGS, cs), 46 | } 47 | } 48 | 49 | /// Free the GPIO pin from the LED struct. 50 | /// 51 | /// # Example 52 | /// 53 | /// ```no_run 54 | /// use lora_e5_bsp::{ 55 | /// hal::{cortex_m, gpio::PortB, pac}, 56 | /// led, 57 | /// }; 58 | /// 59 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 60 | /// 61 | /// let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 62 | /// let mut d5 = cortex_m::interrupt::free(|cs| led::D5::new(gpiob.b5, cs)); 63 | /// // ... use LED 64 | /// let b5 = d5.free(); 65 | /// ``` 66 | pub fn free(self) -> pins::B5 { 67 | self.gpio.free() 68 | } 69 | 70 | /// Steal the LED from whatever is currently using it. 71 | /// 72 | /// This will **not** initialize the GPIO peripheral. 73 | /// 74 | /// # Safety 75 | /// 76 | /// 1. Ensure that the code stealing the LED has exclusive access 77 | /// to the underlying GPIO. 78 | /// Singleton checks are bypassed with this method. 79 | /// 2. You are responsible for setting up the underlying GPIO correctly. 80 | /// No setup will occur when using this method. 81 | /// 82 | /// # Example 83 | /// 84 | /// ``` 85 | /// use lora_e5_bsp::led::D5; 86 | /// 87 | /// // ... setup happens here 88 | /// 89 | /// let d5: D5 = unsafe { D5::steal() }; 90 | /// ``` 91 | pub unsafe fn steal() -> Self { 92 | Self { 93 | gpio: unsafe { Output::steal() }, 94 | } 95 | } 96 | 97 | /// Set the LED on. 98 | pub fn set_on(&mut self) { 99 | self.gpio.set_low().unwrap() 100 | } 101 | 102 | /// Set the LED off. 103 | pub fn set_off(&mut self) { 104 | self.gpio.set_high().unwrap() 105 | } 106 | 107 | /// Toggle the LED state. 108 | pub fn toggle(&mut self) { 109 | self.gpio.set_level(self.gpio.level().not()) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lora-e5-bsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Seeed LoRa-E5 development kit board support package. 2 | 3 | #![cfg_attr(not(test), no_std)] 4 | #![warn(missing_docs)] 5 | 6 | pub mod led; 7 | pub mod pb; 8 | 9 | pub use stm32wlxx_hal as hal; 10 | 11 | use hal::{ 12 | cortex_m::interrupt::CriticalSection, 13 | gpio::{self, Output, OutputArgs, PinState, pins}, 14 | }; 15 | 16 | /// RF switch 17 | #[derive(Debug)] 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 19 | pub struct RfSwitch { 20 | a4: Output, 21 | a5: Output, 22 | } 23 | 24 | impl RfSwitch { 25 | /// Create a new `RfSwitch` struct from GPIOs. 26 | /// 27 | /// # Example 28 | /// 29 | /// ```no_run 30 | /// use lora_e5_bsp::{ 31 | /// RfSwitch, 32 | /// hal::{cortex_m, gpio::PortA, pac}, 33 | /// }; 34 | /// 35 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 36 | /// 37 | /// let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 38 | /// let rfs: RfSwitch = cortex_m::interrupt::free(|cs| RfSwitch::new(gpioa.a4, gpioa.a5, cs)); 39 | /// ``` 40 | pub fn new(a4: pins::A4, a5: pins::A5, cs: &CriticalSection) -> RfSwitch { 41 | const ARGS: OutputArgs = OutputArgs { 42 | speed: gpio::Speed::Fast, 43 | level: gpio::PinState::High, 44 | ot: gpio::OutputType::PushPull, 45 | pull: gpio::Pull::None, 46 | }; 47 | RfSwitch { 48 | a4: Output::new(a4, &ARGS, cs), 49 | a5: Output::new(a5, &ARGS, cs), 50 | } 51 | } 52 | 53 | /// Steal the RF switch from whatever is currently using it. 54 | /// 55 | /// # Safety 56 | /// 57 | /// 1. Ensure that the code stealing the RF switch has exclusive access. 58 | /// Singleton checks are bypassed with this method. 59 | /// 2. You must set up the RF switch pins. 60 | /// No setup will occur when using this method. 61 | /// 62 | /// # Example 63 | /// 64 | /// ```no_run 65 | /// use lora_e5_bsp::{ 66 | /// RfSwitch, 67 | /// hal::{ 68 | /// cortex_m, 69 | /// gpio::{Output, PortA, pins}, 70 | /// pac, 71 | /// }, 72 | /// }; 73 | /// 74 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 75 | /// 76 | /// let pa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 77 | /// cortex_m::interrupt::free(|cs| { 78 | /// let _: Output = Output::default(pa.a4, cs); 79 | /// let _: Output = Output::default(pa.a5, cs); 80 | /// }); 81 | /// 82 | /// // safety: 83 | /// // 1. we have exclusive access to the underlying pins 84 | /// // 2. the pins have been setup 85 | /// let rfs: RfSwitch = unsafe { RfSwitch::steal() }; 86 | /// ``` 87 | #[inline] 88 | pub unsafe fn steal() -> Self { 89 | RfSwitch { 90 | a4: unsafe { Output::steal() }, 91 | a5: unsafe { Output::steal() }, 92 | } 93 | } 94 | 95 | /// Set the RF switch to receive. 96 | /// 97 | /// # Example 98 | /// 99 | /// ```no_run 100 | /// use lora_e5_bsp::{ 101 | /// RfSwitch, 102 | /// hal::{cortex_m, gpio::PortA, pac}, 103 | /// }; 104 | /// 105 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 106 | /// 107 | /// let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 108 | /// let mut rfs: RfSwitch = cortex_m::interrupt::free(|cs| RfSwitch::new(gpioa.a4, gpioa.a5, cs)); 109 | /// rfs.set_rx(); 110 | /// ``` 111 | #[inline] 112 | pub fn set_rx(&mut self) { 113 | self.a5.set_level(PinState::Low); 114 | self.a4.set_level(PinState::High); 115 | } 116 | 117 | /// Set the RF switch to high power transmit. 118 | /// 119 | /// # Example 120 | /// 121 | /// ```no_run 122 | /// use lora_e5_bsp::{ 123 | /// RfSwitch, 124 | /// hal::{cortex_m, gpio::PortA, pac}, 125 | /// }; 126 | /// 127 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 128 | /// 129 | /// let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 130 | /// let mut rfs: RfSwitch = cortex_m::interrupt::free(|cs| RfSwitch::new(gpioa.a4, gpioa.a5, cs)); 131 | /// rfs.set_tx_hp(); 132 | /// ``` 133 | #[inline] 134 | pub fn set_tx_hp(&mut self) { 135 | self.a4.set_level(PinState::Low); 136 | self.a5.set_level(PinState::High); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lora-e5-bsp/src/pb.rs: -------------------------------------------------------------------------------- 1 | //! Push-buttons 2 | 3 | use stm32wlxx_hal::{ 4 | cortex_m::interrupt::CriticalSection, 5 | gpio::{Exti, Input, PinState, Pull, pins}, 6 | }; 7 | 8 | const PULL: Pull = Pull::Up; 9 | 10 | /// Push-button D0. 11 | #[derive(Debug)] 12 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 13 | pub struct D0 { 14 | gpio: Input, 15 | } 16 | 17 | /// Push-button labeled "Boot". 18 | #[derive(Debug)] 19 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 20 | pub struct Boot { 21 | gpio: Input, 22 | } 23 | 24 | /// Simple trait for a push-button 25 | pub trait PushButton { 26 | /// Input pin for the push-button 27 | /// 28 | /// This can be used to access the EXTI trait for the pin. 29 | /// 30 | /// # Example 31 | /// 32 | /// Setup EXTI to fire an interrupt when D0 is pushed. 33 | /// 34 | /// ```no_run 35 | /// use lora_e5_bsp::{ 36 | /// hal::{ 37 | /// cortex_m, 38 | /// gpio::{Exti, ExtiTrg, PortA}, 39 | /// pac, 40 | /// }, 41 | /// pb::{D0, PushButton}, 42 | /// }; 43 | /// 44 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 45 | /// 46 | /// let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 47 | /// let d0 = cortex_m::interrupt::free(|cs| D0::new(gpioa.a0, cs)); 48 | /// 49 | /// ::Pin::setup_exti_c1(&mut dp.EXTI, &mut dp.SYSCFG, ExtiTrg::Falling); 50 | /// ``` 51 | type Pin: Exti; 52 | 53 | /// Returns `True` if the button is currently being pushed. 54 | fn is_pushed(&self) -> bool; 55 | } 56 | 57 | impl PushButton for D0 { 58 | type Pin = pins::A0; 59 | 60 | #[inline] 61 | fn is_pushed(&self) -> bool { 62 | self.gpio.level() == PinState::Low 63 | } 64 | } 65 | 66 | impl PushButton for Boot { 67 | type Pin = pins::B13; 68 | 69 | #[inline] 70 | fn is_pushed(&self) -> bool { 71 | self.gpio.level() == PinState::Low 72 | } 73 | } 74 | 75 | impl D0 { 76 | /// Create a new push-button D0. 77 | /// 78 | /// # Example 79 | /// 80 | /// ```no_run 81 | /// use lora_e5_bsp::{ 82 | /// hal::{cortex_m, gpio::PortA, pac}, 83 | /// pb::{D0, PushButton}, 84 | /// }; 85 | /// 86 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 87 | /// 88 | /// let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 89 | /// let d0 = cortex_m::interrupt::free(|cs| D0::new(gpioa.a0, cs)); 90 | /// ``` 91 | pub fn new(a0: pins::A0, cs: &CriticalSection) -> Self { 92 | Self { 93 | gpio: Input::new(a0, PULL, cs), 94 | } 95 | } 96 | 97 | /// Free the GPIO pin from the push-button struct. 98 | /// 99 | /// # Example 100 | /// 101 | /// ```no_run 102 | /// use lora_e5_bsp::{ 103 | /// hal::{cortex_m, gpio::PortA, pac}, 104 | /// pb::{D0, PushButton}, 105 | /// }; 106 | /// 107 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 108 | /// 109 | /// let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 110 | /// let d0 = cortex_m::interrupt::free(|cs| D0::new(gpioa.a0, cs)); 111 | /// // ... use push button 112 | /// let c0 = d0.free(); 113 | /// ``` 114 | pub fn free(self) -> pins::A0 { 115 | self.gpio.free() 116 | } 117 | 118 | /// Steal the push-button from whatever is currently using it. 119 | /// 120 | /// # Safety 121 | /// 122 | /// 1. Ensure that the code stealing the push-button has exclusive access 123 | /// to the underlying GPIO. 124 | /// Singleton checks are bypassed with this method. 125 | /// 2. You are responsible for setting up the underlying GPIO correctly. 126 | /// No setup will occur when using this method. 127 | /// 128 | /// # Example 129 | /// 130 | /// ``` 131 | /// use lora_e5_bsp::pb::D0; 132 | /// 133 | /// // ... setup happens here 134 | /// 135 | /// let d0: D0 = unsafe { D0::steal() }; 136 | /// ``` 137 | pub unsafe fn steal() -> Self { 138 | Self { 139 | gpio: unsafe { Input::steal() }, 140 | } 141 | } 142 | } 143 | 144 | impl Boot { 145 | /// Create a new boot push-button. 146 | /// 147 | /// # Example 148 | /// 149 | /// ```no_run 150 | /// use lora_e5_bsp::{ 151 | /// hal::{cortex_m, gpio::PortB, pac}, 152 | /// pb::{Boot, PushButton}, 153 | /// }; 154 | /// 155 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 156 | /// 157 | /// let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 158 | /// let boot = cortex_m::interrupt::free(|cs| Boot::new(gpiob.b13, cs)); 159 | /// ``` 160 | pub fn new(b13: pins::B13, cs: &CriticalSection) -> Self { 161 | Self { 162 | gpio: Input::new(b13, PULL, cs), 163 | } 164 | } 165 | 166 | /// Free the GPIO pin from the push-button struct. 167 | /// 168 | /// # Example 169 | /// 170 | /// ```no_run 171 | /// use lora_e5_bsp::{ 172 | /// hal::{cortex_m, gpio::PortB, pac}, 173 | /// pb::{Boot, PushButton}, 174 | /// }; 175 | /// 176 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 177 | /// 178 | /// let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 179 | /// let boot = cortex_m::interrupt::free(|cs| Boot::new(gpiob.b13, cs)); 180 | /// // ... use push button 181 | /// let b13 = boot.free(); 182 | /// ``` 183 | pub fn free(self) -> pins::B13 { 184 | self.gpio.free() 185 | } 186 | 187 | /// Steal the push-button from whatever is currently using it. 188 | /// 189 | /// This will **not** initialize the GPIO peripheral. 190 | /// 191 | /// # Safety 192 | /// 193 | /// 1. Ensure that the code stealing the push-button has exclusive access 194 | /// to the underlying GPIO. 195 | /// Singleton checks are bypassed with this method. 196 | /// 2. You are responsible for setting up the underlying GPIO correctly. 197 | /// No setup will occur when using this method. 198 | /// 199 | /// # Example 200 | /// 201 | /// ``` 202 | /// use lora_e5_bsp::pb::Boot; 203 | /// 204 | /// // ... setup happens here 205 | /// 206 | /// let boot: Boot = unsafe { Boot::steal() }; 207 | /// ``` 208 | pub unsafe fn steal() -> Self { 209 | Self { 210 | gpio: unsafe { Input::steal() }, 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | /* Memory for the NUCLEO-WL55JC2 */ 2 | MEMORY 3 | { 4 | /* See section 4.3.1 "Flash memory organization" in the reference manual */ 5 | FLASH : ORIGIN = 0x8000000, LENGTH = 256k 6 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 7 | } 8 | -------------------------------------------------------------------------------- /nucleo-wl55jc-bsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nucleo-wl55jc-bsp" 3 | description = "Board support package for the NUCLEO-WL55JC" 4 | readme = "README.md" 5 | keywords = ["arm", "cortex-m", "stm32", "bsp", "nucleo"] 6 | categories = ["embedded", "hardware-support", "no-std"] 7 | 8 | authors.workspace = true 9 | edition.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | version.workspace = true 14 | 15 | [features] 16 | chrono = ["stm32wlxx-hal/chrono"] 17 | defmt = ["stm32wlxx-hal/defmt", "dep:defmt"] 18 | embedded-time = ["stm32wlxx-hal/embedded-time"] 19 | rt = ["stm32wlxx-hal/rt"] 20 | stm32wl5x_cm0p = ["stm32wlxx-hal/stm32wl5x_cm0p"] 21 | stm32wl5x_cm4 = ["stm32wlxx-hal/stm32wl5x_cm4"] 22 | 23 | [dependencies.stm32wlxx-hal] 24 | version = "=0.6.1" 25 | path = "../hal" 26 | 27 | [dependencies.defmt] 28 | version = "1" 29 | optional = true 30 | 31 | [package.metadata.docs.rs] 32 | all-features = false 33 | features = ["stm32wl5x_cm4", "rt", "embedded-time", "chrono"] 34 | rustdoc-args = ["--cfg", "docsrs"] 35 | -------------------------------------------------------------------------------- /nucleo-wl55jc-bsp/README.md: -------------------------------------------------------------------------------- 1 | # NUCLEO-WL55JC Board Support Package 2 | 3 | Board support for the NUCLEO-WL55JC development board. 4 | 5 | This crate extends the [stm32wlxx-hal] with board specific hardware, see that crate for more information. 6 | 7 | ## Usage 8 | 9 | ```toml 10 | [dependencies.nucleo-wl55jc-bsp] 11 | version = "0.6.1" 12 | features = [ 13 | # required: build for core 1 14 | # This is future proofing for when the HAL has APIs for core 2 15 | "stm32wl5x_cm4", 16 | # optional: use the cortex-m-rt interrupt interface 17 | "rt", 18 | # optional: use defmt 19 | "defmt", 20 | # optional: enable conversions with embedded-time types 21 | "embedded-time", 22 | # optional: use the real time clock (RTC) 23 | "chrono", 24 | ] 25 | ``` 26 | 27 | ## Flashing 28 | 29 | To flash this board with various rust utilities such as `probe-run`, `cargo-embed`, and `cargo-flash` use the `--connected-under-reset` flag. 30 | 31 | ⚠️ You must use recent versions of `probe-rs` based tools to avoid bugs with the STM32WL ⚠️ 32 | 33 | * `cargo-embed` >=0.12.0 34 | * `cargo-flash` >=0.12.0 35 | * `probe-run` >=0.3.1 36 | 37 | [stm32wlxx-hal]: https://github.com/stm32-rs/stm32wlxx-hal 38 | -------------------------------------------------------------------------------- /nucleo-wl55jc-bsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! NUCLEO-WL55JC board support package. 2 | 3 | #![cfg_attr(not(test), no_std)] 4 | #![warn(missing_docs)] 5 | 6 | pub mod led; 7 | pub mod pb; 8 | 9 | pub use stm32wlxx_hal as hal; 10 | 11 | use hal::{ 12 | cortex_m::interrupt::CriticalSection, 13 | gpio::{self, Output, OutputArgs, PinState, pins}, 14 | }; 15 | 16 | /// RF switch. 17 | #[derive(Debug)] 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 19 | pub struct RfSwitch { 20 | fe_ctrl1: Output, 21 | fe_ctrl2: Output, 22 | fe_ctrl3: Output, 23 | } 24 | 25 | impl RfSwitch { 26 | /// Create a new `RfSwitch` struct from GPIOs. 27 | /// 28 | /// # Example 29 | /// 30 | /// ```no_run 31 | /// use nucleo_wl55jc_bsp::{ 32 | /// RfSwitch, 33 | /// hal::{cortex_m, gpio::PortC, pac}, 34 | /// }; 35 | /// 36 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 37 | /// 38 | /// let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 39 | /// let rfs: RfSwitch = 40 | /// cortex_m::interrupt::free(|cs| RfSwitch::new(gpioc.c3, gpioc.c4, gpioc.c5, cs)); 41 | /// ``` 42 | pub fn new(c3: pins::C3, c4: pins::C4, c5: pins::C5, cs: &CriticalSection) -> RfSwitch { 43 | const ARGS: OutputArgs = OutputArgs { 44 | speed: gpio::Speed::Fast, 45 | level: gpio::PinState::High, 46 | ot: gpio::OutputType::PushPull, 47 | pull: gpio::Pull::None, 48 | }; 49 | RfSwitch { 50 | fe_ctrl1: Output::new(c4, &ARGS, cs), 51 | fe_ctrl2: Output::new(c5, &ARGS, cs), 52 | fe_ctrl3: Output::new(c3, &ARGS, cs), 53 | } 54 | } 55 | 56 | /// Steal the RF switch from whatever is currently using it. 57 | /// 58 | /// # Safety 59 | /// 60 | /// 1. Ensure that the code stealing the RF switch has exclusive access. 61 | /// Singleton checks are bypassed with this method. 62 | /// 2. You must set up the RF switch pins. 63 | /// No setup will occur when using this method. 64 | /// 65 | /// # Example 66 | /// 67 | /// ```no_run 68 | /// use nucleo_wl55jc_bsp::{ 69 | /// RfSwitch, 70 | /// hal::{ 71 | /// cortex_m, 72 | /// gpio::{Output, PortC, pins}, 73 | /// pac, 74 | /// }, 75 | /// }; 76 | /// 77 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 78 | /// 79 | /// let pc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 80 | /// cortex_m::interrupt::free(|cs| { 81 | /// let _: Output = Output::default(pc.c3, cs); 82 | /// let _: Output = Output::default(pc.c4, cs); 83 | /// let _: Output = Output::default(pc.c5, cs); 84 | /// }); 85 | /// 86 | /// // safety: 87 | /// // 1. we have exclusive access to the underlying pins 88 | /// // 2. the pins have been setup 89 | /// let rfs: RfSwitch = unsafe { RfSwitch::steal() }; 90 | /// ``` 91 | #[inline] 92 | pub unsafe fn steal() -> Self { 93 | RfSwitch { 94 | fe_ctrl1: unsafe { Output::steal() }, 95 | fe_ctrl2: unsafe { Output::steal() }, 96 | fe_ctrl3: unsafe { Output::steal() }, 97 | } 98 | } 99 | 100 | /// Set the RF switch to receive. 101 | /// 102 | /// # Example 103 | /// 104 | /// ```no_run 105 | /// use nucleo_wl55jc_bsp::{ 106 | /// RfSwitch, 107 | /// hal::{cortex_m, gpio::PortC, pac}, 108 | /// }; 109 | /// 110 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 111 | /// 112 | /// let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 113 | /// let mut rfs: RfSwitch = 114 | /// cortex_m::interrupt::free(|cs| RfSwitch::new(gpioc.c3, gpioc.c4, gpioc.c5, cs)); 115 | /// rfs.set_rx() 116 | /// ``` 117 | #[inline] 118 | pub fn set_rx(&mut self) { 119 | self.fe_ctrl1.set_level(PinState::High); 120 | self.fe_ctrl2.set_level(PinState::Low); 121 | self.fe_ctrl3.set_level(PinState::High); 122 | } 123 | 124 | /// Set the RF switch to low power transmit. 125 | /// 126 | /// # Example 127 | /// 128 | /// ```no_run 129 | /// use nucleo_wl55jc_bsp::{ 130 | /// RfSwitch, 131 | /// hal::{cortex_m, gpio::PortC, pac}, 132 | /// }; 133 | /// 134 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 135 | /// 136 | /// let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 137 | /// let mut rfs: RfSwitch = 138 | /// cortex_m::interrupt::free(|cs| RfSwitch::new(gpioc.c3, gpioc.c4, gpioc.c5, cs)); 139 | /// rfs.set_tx_lp() 140 | /// ``` 141 | #[inline] 142 | pub fn set_tx_lp(&mut self) { 143 | self.fe_ctrl1.set_level(PinState::High); 144 | self.fe_ctrl2.set_level(PinState::High); 145 | self.fe_ctrl3.set_level(PinState::High); 146 | } 147 | 148 | /// Set the RF switch to high power transmit. 149 | /// 150 | /// # Example 151 | /// 152 | /// ```no_run 153 | /// use nucleo_wl55jc_bsp::{ 154 | /// RfSwitch, 155 | /// hal::{cortex_m, gpio::PortC, pac}, 156 | /// }; 157 | /// 158 | /// let mut dp: pac::Peripherals = pac::Peripherals::take().unwrap(); 159 | /// 160 | /// let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 161 | /// let mut rfs: RfSwitch = 162 | /// cortex_m::interrupt::free(|cs| RfSwitch::new(gpioc.c3, gpioc.c4, gpioc.c5, cs)); 163 | /// rfs.set_tx_hp() 164 | /// ``` 165 | #[inline] 166 | pub fn set_tx_hp(&mut self) { 167 | self.fe_ctrl2.set_level(PinState::High); 168 | self.fe_ctrl1.set_level(PinState::Low); 169 | self.fe_ctrl3.set_level(PinState::High); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | -------------------------------------------------------------------------------- /testsuite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testsuite" 3 | publish = false 4 | 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | rust-version.workspace = true 10 | version.workspace = true 11 | 12 | [[bin]] 13 | name = "adc" 14 | path = "src/adc.rs" 15 | harness = false 16 | 17 | [[bin]] 18 | name = "aes" 19 | path = "src/aes.rs" 20 | harness = false 21 | 22 | [[bin]] 23 | name = "dac" 24 | path = "src/dac.rs" 25 | harness = false 26 | 27 | [[bin]] 28 | name = "flash" 29 | path = "src/flash.rs" 30 | harness = false 31 | 32 | [[bin]] 33 | name = "i2c" 34 | path = "src/i2c.rs" 35 | harness = false 36 | 37 | [[bin]] 38 | name = "info" 39 | path = "src/info.rs" 40 | harness = false 41 | 42 | [[bin]] 43 | name = "lptim" 44 | path = "src/lptim.rs" 45 | harness = false 46 | 47 | [[bin]] 48 | name = "pka" 49 | path = "src/pka.rs" 50 | harness = false 51 | 52 | [[bin]] 53 | name = "rcc" 54 | path = "src/rcc.rs" 55 | harness = false 56 | 57 | [[bin]] 58 | name = "rng" 59 | path = "src/rng.rs" 60 | harness = false 61 | 62 | [[bin]] 63 | name = "rtc" 64 | path = "src/rtc.rs" 65 | harness = false 66 | 67 | [[bin]] 68 | name = "spi" 69 | path = "src/spi.rs" 70 | harness = false 71 | 72 | [[bin]] 73 | name = "subghz" 74 | path = "src/subghz.rs" 75 | harness = false 76 | 77 | [[bin]] 78 | name = "uart" 79 | path = "src/uart.rs" 80 | harness = false 81 | 82 | [dependencies] 83 | aes-gcm = { version = "0.10.3", default-features = false, features = ["aes"] } 84 | cortex-m = { version = "0.7", features = ["critical-section-single-core"] } 85 | cortex-m-rt = "0.7" 86 | defmt = "1" 87 | defmt-rtt = "1" 88 | defmt-test = "0.4" 89 | ecdsa = { version = "0.16", default-features = false, features = ["arithmetic", "hazmat"] } 90 | embedded-time = "0.12" 91 | hex-literal = "1" 92 | itertools = { version = "0.14", default-features = false } 93 | nb = "1" 94 | p256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdsa"] } 95 | panic-probe = { version = "1", features = ["print-defmt" ] } 96 | rand = { version = "0.9", default-features = false } 97 | rand_chacha = { version = "0.9", default-features = false } 98 | static_assertions = "1" 99 | 100 | [dependencies.nucleo-wl55jc-bsp] 101 | path = "../nucleo-wl55jc-bsp" 102 | features = ["stm32wl5x_cm4", "defmt", "rt", "chrono"] 103 | -------------------------------------------------------------------------------- /testsuite/README.md: -------------------------------------------------------------------------------- 1 | # Testsuite 2 | 3 | This workspace contains on-target tests using [defmt-test]. 4 | 5 | The tests run on the NUCLEO-WL55JC2, you can find a place purchase this 6 | [here](https://www.st.com/en/evaluation-tools/nucleo-wl55jc.html#sample-buy). 7 | 8 | These tests will run automatically as part of CI for every pull-request. 9 | 10 | ## Quickstart 11 | 12 | * `rustup target add --toolchain stable thumbv7em-none-eabi` ([rustup]) 13 | * `cargo install probe-rs --features cli` ([probe-rs]) 14 | * Linux users: Add udev rules 15 | 16 | ```text 17 | # /etc/udev/rules.d/99-stm.rules 18 | SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374e", MODE="0666" 19 | ``` 20 | 21 | * Linux users: run `sudo udevadm control --reload-rules && sudo udevadm trigger` 22 | * Connect the nucleo board to your PC via USB 23 | * `cargo test -p testsuite --target thumbv7em-none-eabi --bin pka` 24 | 25 | ## Sample output 26 | 27 | ```console 28 | $ DEFMT_LOG=info cargo test -p testsuite --target thumbv7em-none-eabi --bin pka 29 | Finished dev [optimized + debuginfo] target(s) in 0.01s 30 | Running `probe-rs run --chip STM32WLE5JCIx --connect-under-reset target/thumbv7em-none-eabi/debug/pka` 31 | (HOST) INFO flashing program (17.31 KiB) 32 | (HOST) INFO success! 33 | ──────────────────────────────────────────────────────────────────────────────── 34 | 0.000008 INFO (1/10) running `ecdsa_sign`... 35 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 36 | 0.108633 INFO (2/10) running `ecdsa_sign_nb`... 37 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 38 | 0.217258 INFO (3/10) running `ecdsa_sign_ram_error`... 39 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 40 | 0.436387 INFO (4/10) running `ecdsa_sign_mode_error`... 41 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 42 | 0.655516 INFO (5/10) running `ecdsa_verify`... 43 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 44 | 0.874646 INFO (6/10) running `ecdsa_verify_nb`... 45 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 46 | 1.093774 INFO (7/10) running `ecdsa_verify_ram_err`... 47 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 48 | 1.202398 INFO (8/10) running `ecdsa_verify_mode_err`... 49 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 50 | 1.311021 INFO (9/10) running `ecdsa_verify_invalid_err`... 51 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 52 | 1.527159 INFO (10/10) running `ecdsa_doc_keypair`... 53 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 54 | 1.845845 INFO all tests passed! 55 | └─ pka::tests::__defmt_test_entry @ testsuite/src/pka.rs:60 56 | ──────────────────────────────────────────────────────────────────────────────── 57 | (HOST) INFO device halted without error 58 | ``` 59 | 60 | ## SubGhz Tests 61 | 62 | The subghz on-target tests require two nucleo boards, one for transmitting, 63 | and one for receiving. 64 | Run the `subghz` binary twice on two different boards. 65 | 66 | Assuming both boards are connected to the same system you will have to pass a 67 | specific probe to each. 68 | 69 | ```console 70 | $ probe-rs list 71 | The following devices were found: 72 | [0]: STLink V3 (VID: 0483, PID: 374e, Serial: 001D00145553500A20393256, STLink) 73 | [1]: STLink V3 (VID: 0483, PID: 374e, Serial: 001600345553500A20393256, STLink) 74 | $ DEFMT_LOG=info cargo test -p testsuite --target thumbv7em-none-eabi --bin subghz -- --probe 001D00145553500A20393256 75 | $ DEFMT_LOG=info cargo test -p testsuite --target thumbv7em-none-eabi --bin subghz -- --probe 001600345553500A20393256 76 | ``` 77 | 78 | [defmt-test]: https://crates.io/crates/defmt-test 79 | [probe-rs]: https://github.com/probe-rs/probe-rs 80 | [rustup]: https://rustup.rs/ 81 | -------------------------------------------------------------------------------- /testsuite/runall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | This is a helper script to run test binaries in parallel with two probes. 5 | 6 | Build the binaries before running this script:: 7 | 8 | cargo test -p testsuite --target thumbv7em-none-eabi --bins --no-run 9 | """ 10 | 11 | from typing import List, NamedTuple, Optional, Tuple 12 | import argparse 13 | import asyncio 14 | import os 15 | import sys 16 | import time 17 | 18 | 19 | class TestResult(NamedTuple): 20 | rc: int 21 | elf_abs_path: str 22 | probe: str 23 | elapsed: float 24 | 25 | def elf_filename(self) -> str: 26 | return os.path.basename(self.elf_abs_path) 27 | 28 | def test_name(self) -> str: 29 | # assumes the ELF is in the form of "rtc-1794a848b0c66d5d" 30 | return self.elf_filename().split("-")[0] 31 | 32 | def status(self) -> str: 33 | return "PASS" if self.passed() else "FAIL" 34 | 35 | def passed(self) -> bool: 36 | return self.rc == 0 37 | 38 | 39 | def find_elfs(elf_dir: str) -> Tuple[List[str], str]: 40 | """ 41 | ``cargo`` does not output stable names for the test binaries. 42 | 43 | This scans for ELF files, returning a tuple of 44 | ``(test_elf_files, subghz_test_elf_file)``. 45 | """ 46 | test_elf_files: List[str] = [] 47 | subghz_test_elf_file: Optional[str] = None 48 | 49 | for file in os.listdir(elf_dir): 50 | # runnable ELFs will not have an extension 51 | if os.path.splitext(file)[1] != "": 52 | continue 53 | 54 | file_abs_path = os.path.join(elf_dir, file) 55 | with open(file_abs_path, "rb") as f: 56 | magic = f.read(4) 57 | 58 | if magic == b"\x7FELF": 59 | if "subghz" in file: 60 | if subghz_test_elf_file is not None: 61 | raise Exception( 62 | "subghz_test_elf_file already set " 63 | f"subghz_test_elf_file={subghz_test_elf_file}" 64 | ) 65 | 66 | subghz_test_elf_file = file_abs_path 67 | else: 68 | test_elf_files.append(file_abs_path) 69 | 70 | if subghz_test_elf_file is None: 71 | raise Exception("subghz test ELF file not found") 72 | 73 | return (test_elf_files, subghz_test_elf_file) 74 | 75 | 76 | async def probe_run(elf_path: str, probe: str, log_prefix: str) -> TestResult: 77 | print(f"[{log_prefix}] Running {elf_path}") 78 | start = time.monotonic() 79 | proc = await asyncio.create_subprocess_exec( 80 | "probe-rs", 81 | "run", 82 | "--chip", 83 | "STM32WLE5JCIx", 84 | "--catch-hardfault", 85 | "--connect-under-reset", 86 | elf_path, 87 | "--probe", 88 | probe, 89 | stdout=asyncio.subprocess.PIPE, 90 | stderr=asyncio.subprocess.STDOUT, 91 | ) 92 | rc = await proc.wait() 93 | elapsed = time.monotonic() - start 94 | 95 | for line in ( 96 | (await proc.stdout.read()) 97 | .decode(encoding="UTF-8", errors="backslashreplace") 98 | .splitlines() 99 | ): 100 | print(f"[{log_prefix}] {line.rstrip()}") 101 | 102 | return TestResult(rc=rc, elf_abs_path=elf_path, probe=probe, elapsed=elapsed) 103 | 104 | 105 | async def probe_worker( 106 | test_elf_queue: asyncio.Queue, probe: str, log_prefix: str 107 | ) -> List[TestResult]: 108 | print(f"[{log_prefix}] Using probe {probe}") 109 | 110 | ret = [] 111 | while True: 112 | try: 113 | test_elf_path = test_elf_queue.get_nowait() 114 | except asyncio.QueueEmpty: 115 | return ret 116 | else: 117 | result = await probe_run(test_elf_path, probe, log_prefix) 118 | ret.append(result) 119 | 120 | return ret 121 | 122 | 123 | def flatten(t: List[list]) -> list: 124 | return [item for sublist in t for item in sublist] 125 | 126 | 127 | async def main() -> int: 128 | this_dir = os.path.dirname(os.path.abspath(__file__)) 129 | repo_root = os.path.abspath(os.path.join(this_dir, "..")) 130 | default_elf_dir = os.path.join( 131 | repo_root, "target", "thumbv7em-none-eabi", "debug", "deps" 132 | ) 133 | parser = argparse.ArgumentParser(description="Helper to run all on-target tests") 134 | parser.add_argument( 135 | "-d", 136 | "--elf-dir", 137 | default=default_elf_dir, 138 | type=str, 139 | help="Path to directory containing ELFs to flash", 140 | ) 141 | parser.add_argument("probe", nargs=2, help="Serial number of the probes to use") 142 | args = parser.parse_args() 143 | 144 | (test_elf_files, subghz_test_elf_file) = find_elfs(args.elf_dir) 145 | 146 | print("Running subghz test") 147 | subghz_results = await asyncio.gather( 148 | probe_run(subghz_test_elf_file, args.probe[0], "A"), 149 | probe_run(subghz_test_elf_file, args.probe[1], "B"), 150 | ) 151 | 152 | num_tests: int = len(test_elf_files) 153 | print(f"Running {num_tests} tests") 154 | 155 | test_elf_queue = asyncio.Queue() 156 | for test in test_elf_files: 157 | test_elf_queue.put_nowait(test) 158 | 159 | test_results = await asyncio.gather( 160 | probe_worker(test_elf_queue, args.probe[0], "A"), 161 | probe_worker(test_elf_queue, args.probe[1], "B"), 162 | ) 163 | test_results = flatten(test_results) 164 | test_results.extend(subghz_results) 165 | 166 | fail: bool = False 167 | for result in test_results: 168 | if not result.passed(): 169 | fail = True 170 | 171 | print( 172 | f"{result.status()} " 173 | f"{result.test_name()} " 174 | f"{result.elapsed:.3f}s " 175 | f"{result.probe}" 176 | ) 177 | 178 | if fail: 179 | return 1 180 | else: 181 | return 0 182 | 183 | 184 | if __name__ == "__main__": 185 | sys.exit(asyncio.run(main())) 186 | -------------------------------------------------------------------------------- /testsuite/src/dac.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use defmt_rtt as _; // global logger 6 | use nucleo_wl55jc_bsp::hal::{ 7 | adc::{self, Adc}, 8 | cortex_m::{self, delay::Delay, peripheral::syst::SystClkSource}, 9 | dac::{Dac, ModeChip, ModePin}, 10 | gpio::{Analog, PortA}, 11 | pac, rcc, 12 | }; 13 | use panic_probe as _; 14 | 15 | #[defmt_test::tests] 16 | mod tests { 17 | use super::*; 18 | 19 | struct TestArgs { 20 | adc: Adc, 21 | dac: Dac, 22 | delay: Delay, 23 | } 24 | 25 | #[init] 26 | fn init() -> TestArgs { 27 | cortex_m::interrupt::free(|cs| { 28 | let mut dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 29 | let cp: pac::CorePeripherals = unwrap!(pac::CorePeripherals::take()); 30 | 31 | unsafe { rcc::set_sysclk_msi_max(&mut dp.FLASH, &mut dp.PWR, &mut dp.RCC, cs) }; 32 | 33 | let delay: Delay = 34 | Delay::new(cp.SYST, rcc::cpu1_systick_hz(&dp.RCC, SystClkSource::Core)); 35 | 36 | dp.RCC.cr.modify(|_, w| w.hsion().set_bit()); 37 | while dp.RCC.cr.read().hsirdy().is_not_ready() {} 38 | 39 | dp.RCC.csr.modify(|_, w| w.lsion().on()); 40 | while dp.RCC.csr.read().lsirdy().is_not_ready() {} 41 | 42 | let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 43 | let mut dac: Dac = Dac::new(dp.DAC, &mut dp.RCC); 44 | let adc: Adc = Adc::new(dp.ADC, adc::Clk::RccHsi, &mut dp.RCC); 45 | 46 | dac.set_mode_pin(Analog::new(gpioa.a10, cs), ModePin::NormChipBuf); 47 | 48 | TestArgs { adc, dac, delay } 49 | }) 50 | } 51 | 52 | #[test] 53 | fn pin_output(ta: &mut TestArgs) { 54 | ta.dac.setup_soft_trigger(); 55 | ta.dac.soft_trigger(2048); 56 | defmt::assert_eq!(ta.dac.out(), 2048); 57 | 58 | // insert a delay here if you want to poke around with a voltmeter 59 | // should be appx 1.65V 60 | // ta.delay.delay_ms(10_000); 61 | } 62 | 63 | #[test] 64 | fn loopback(ta: &mut TestArgs) { 65 | ta.adc.start_disable(); 66 | while !ta.adc.is_disabled() {} 67 | ta.adc.calibrate(&mut ta.delay); 68 | ta.adc.enable(); 69 | ta.adc.set_max_sample_time(); 70 | 71 | ta.dac.disable(); 72 | unwrap!(ta.dac.set_mode_chip(ModeChip::Norm)); 73 | ta.dac.setup_soft_trigger(); 74 | ta.dac.soft_trigger(1024); 75 | 76 | let out: u16 = ta.dac.out(); 77 | let sample: u16 = ta.adc.dac(); 78 | defmt::info!("DAC out: {}", out); 79 | defmt::info!("ADC in: {}", sample); 80 | let delta: i32 = (i32::from(sample) - i32::from(ta.dac.out())).abs(); 81 | 82 | defmt::assert!(delta < 20); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /testsuite/src/i2c.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use defmt_rtt as _; // global logger 6 | use nucleo_wl55jc_bsp::hal::{ 7 | cortex_m, 8 | embedded_hal::blocking::i2c::WriteRead, 9 | gpio::{PortA, PortB, pins}, 10 | i2c::{I2c1, I2c2}, 11 | pac::{self, interrupt}, 12 | rcc, 13 | }; 14 | use panic_probe as _; 15 | 16 | const LOOPBACK_ADDR: u8 = 0x77; 17 | const LOOPBACK_DATA_IN: u8 = 0xD0; 18 | const LOOPBACK_DATA_OUT: u8 = 0xA5; 19 | const I2C_FREQUENCY: u32 = 100_000; 20 | 21 | #[defmt_test::tests] 22 | mod tests { 23 | use super::*; 24 | 25 | #[init] 26 | fn init() -> I2c1<(pins::B8, pins::B7)> { 27 | cortex_m::interrupt::free(|cs| { 28 | let mut dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 29 | 30 | unsafe { rcc::set_sysclk_msi_max(&mut dp.FLASH, &mut dp.PWR, &mut dp.RCC, cs) }; 31 | let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 32 | let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 33 | 34 | // initialize I2C2 clocks and pins 35 | let i2c2 = I2c2::new( 36 | dp.I2C2, 37 | (gpioa.a12, gpioa.a11), 38 | I2C_FREQUENCY, 39 | &mut dp.RCC, 40 | true, 41 | cs, 42 | ); 43 | let (i2c2, _) = i2c2.free(); 44 | 45 | // disable i2c2 to reconfigure for secondary mode 46 | i2c2.cr1.modify(|_, w| w.pe().disabled()); 47 | 48 | // set the i2c2 secondary address to 0x77 49 | i2c2.oar1.write(|w| { 50 | w.oa1en() 51 | .enabled() 52 | .oa1mode() 53 | .bit7() 54 | .oa1() 55 | .bits((LOOPBACK_ADDR << 1) as u16) 56 | }); 57 | i2c2.cr1.modify(|_, w| { 58 | w.gcen().set_bit(); // general call enable 59 | w.errie().enabled(); // enable error IRQs 60 | w.addrie().enabled(); // secondary address match IRQ 61 | w.pe().enabled() // re-enable peripheral 62 | }); 63 | 64 | unsafe { 65 | pac::NVIC::unmask(pac::Interrupt::I2C2_EV); 66 | pac::NVIC::unmask(pac::Interrupt::I2C2_ER); 67 | } 68 | 69 | I2c1::new( 70 | dp.I2C1, 71 | (gpiob.b8, gpiob.b7), 72 | I2C_FREQUENCY, 73 | &mut dp.RCC, 74 | false, 75 | cs, 76 | ) 77 | }) 78 | } 79 | 80 | #[test] 81 | fn sht31_measurement(i2c: &mut I2c1<(pins::B8, pins::B7)>) { 82 | defmt::warn!( 83 | "A SHT31 sensor must be connected to the board on pins B8 (SCL) & B7 (SDA) for this test to work" 84 | ); 85 | let cmd: [u8; 2] = [0x2C, 0x06]; 86 | let mut response: [u8; 6] = [0; 6]; 87 | 88 | let result = i2c.write_read(0x44, &cmd, &mut response); 89 | match result { 90 | Ok(()) => defmt::info!("Bytes received: {:x}", response), 91 | Err(e) => defmt::error!("I2C error: {}", e), 92 | } 93 | } 94 | 95 | #[test] 96 | fn loopback(i2c: &mut I2c1<(pins::B8, pins::B7)>) { 97 | defmt::warn!( 98 | "I2C1 pins B8 (SCL) and B7 (SDA) must be connected to I2C pins A12 (SCL) and A11 (SDA) for this test to pass" 99 | ); 100 | 101 | let cmd: [u8; 1] = [LOOPBACK_DATA_IN]; 102 | let mut response: [u8; 1] = [0; 1]; 103 | 104 | let result = i2c.write_read(LOOPBACK_ADDR, &cmd, &mut response); 105 | match result { 106 | Ok(()) => defmt::assert_eq!(LOOPBACK_DATA_OUT, response[0]), 107 | Err(e) => { 108 | defmt::panic!("I2C error: {}", e); 109 | } 110 | } 111 | } 112 | } 113 | 114 | #[interrupt] 115 | #[allow(non_snake_case)] 116 | fn I2C2_EV() { 117 | let dp: pac::Peripherals = unsafe { pac::Peripherals::steal() }; 118 | let isr = dp.I2C2.isr.read(); 119 | defmt::debug!("I2C2 ISR={:#08X}", isr.bits()); 120 | 121 | if isr.rxne().is_not_empty() { 122 | let rxdr: u8 = dp.I2C2.rxdr.read().rxdata().bits(); 123 | defmt::assert_eq!(rxdr, LOOPBACK_DATA_IN); 124 | } 125 | 126 | if isr.addr().is_match() { 127 | dp.I2C2.txdr.write(|w| w.txdata().bits(LOOPBACK_DATA_OUT)); 128 | dp.I2C2.icr.write(|w| w.addrcf().set_bit()); 129 | } 130 | } 131 | 132 | #[interrupt] 133 | #[allow(non_snake_case)] 134 | fn I2C2_ER() { 135 | cortex_m::interrupt::disable(); 136 | let dp: pac::Peripherals = unsafe { pac::Peripherals::steal() }; 137 | defmt::panic!("I2C2 error ISR={:#08X}", dp.I2C2.isr.read().bits()); 138 | } 139 | -------------------------------------------------------------------------------- /testsuite/src/info.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use defmt_rtt as _; // global logger 6 | use nucleo_wl55jc_bsp::hal::{ 7 | cortex_m, 8 | info::{self, Core, Uid64}, 9 | pac::{self, DWT}, 10 | rcc, 11 | }; 12 | use panic_probe as _; 13 | 14 | const FREQ: u32 = 48_000_000; 15 | const CYC_PER_MICRO: u32 = FREQ / 1000 / 1000; 16 | 17 | // WARNING will wrap-around eventually, use this for relative timing only 18 | defmt::timestamp!("{=u32:us}", DWT::cycle_count() / CYC_PER_MICRO); 19 | 20 | #[defmt_test::tests] 21 | mod tests { 22 | use super::*; 23 | 24 | #[init] 25 | fn init() { 26 | let mut cp: pac::CorePeripherals = unwrap!(pac::CorePeripherals::take()); 27 | let mut dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 28 | 29 | cortex_m::interrupt::free(|cs| unsafe { 30 | rcc::set_sysclk_msi_max(&mut dp.FLASH, &mut dp.PWR, &mut dp.RCC, cs) 31 | }); 32 | 33 | cp.DCB.enable_trace(); 34 | cp.DWT.enable_cycle_counter(); 35 | cp.DWT.set_cycle_count(0); 36 | } 37 | 38 | #[test] 39 | fn core() { 40 | defmt::assert_eq!(Core::CT, Core::Cm4); 41 | defmt::assert_eq!(Core::from_cpuid(), Core::Cm4); 42 | } 43 | 44 | #[test] 45 | fn flash_size() { 46 | defmt::assert_eq!(info::flash_size_kibibyte(), 256); 47 | defmt::assert_eq!(info::flash_size(), 256 * 1024); 48 | } 49 | 50 | #[test] 51 | fn uid64() { 52 | defmt::assert_eq!(Uid64::from_device().dev_id(), 0x15); 53 | defmt::assert_eq!(Uid64::from_device().company_id(), 0x0080E1); 54 | defmt::assert_eq!(Uid64::from_device().devnum(), Uid64::read_devnum()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /testsuite/src/lptim.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use defmt_rtt as _; // global logger 6 | use nucleo_wl55jc_bsp::hal::{ 7 | cortex_m, 8 | embedded_hal::digital::v2::ToggleableOutputPin, 9 | embedded_hal::timer::CountDown, 10 | gpio::{Output, PortA, PortB, pins}, 11 | lptim::{self, Filter, LpTim, LpTim1, LpTim2, LpTim3, Prescaler, TrgPol}, 12 | pac::{self, DWT}, 13 | rcc, 14 | }; 15 | use panic_probe as _; 16 | 17 | const FREQ: u32 = 48_000_000; 18 | const CYC_PER_US: u32 = FREQ / 1000 / 1000; 19 | 20 | // WARNING will wrap-around eventually, use this for relative timing only 21 | defmt::timestamp!("{=u32:us}", DWT::cycle_count() / CYC_PER_US); 22 | 23 | fn test_clk_src(lptim: &mut LPTIM, src: lptim::Clk, rcc: &pac::RCC) 24 | where 25 | LPTIM: LpTim + CountDown, 26 | ::Time: From, 27 | { 28 | defmt::assert_eq!(LPTIM::clk(rcc), src); 29 | let lptim_freq: u32 = lptim.hz().to_integer(); 30 | 31 | let cycles: u16 = u16::try_from(lptim_freq / 32).unwrap_or(u16::MAX); 32 | let start: u32 = DWT::cycle_count(); 33 | lptim.start(cycles); 34 | unwrap!(nb::block!(lptim.wait()).ok()); 35 | let end: u32 = DWT::cycle_count(); 36 | 37 | // compare elapsed lptim cycles to elapsed CPU cycles 38 | let elapsed: u32 = end.wrapping_sub(start); 39 | let expected_elapsed: u32 = u32::from(cycles) * (FREQ / lptim_freq); 40 | 41 | let elapsed_upper: u32 = if lptim_freq > 10_000 { 42 | // 6.25% tolerance 43 | expected_elapsed + expected_elapsed / 16 44 | } else { 45 | // upper limit gets massively skewed at low frequencies due to 46 | // delays when enabling the timer 47 | expected_elapsed.saturating_mul(3) 48 | }; 49 | 50 | // the embedded-hal trait guarantees **at least** n cycles 51 | // this _should_ just be `expected_elapsed`, but there is some 52 | // measurement error with independent clock sources 53 | let elapsed_lower: u32 = expected_elapsed - expected_elapsed / 128; 54 | 55 | defmt::assert!( 56 | elapsed_lower <= elapsed && elapsed <= elapsed_upper, 57 | "Timer is incorrect: {} <= {} <= {}", 58 | elapsed_lower, 59 | elapsed, 60 | elapsed_upper 61 | ); 62 | } 63 | 64 | #[defmt_test::tests] 65 | mod tests { 66 | use super::*; 67 | 68 | struct TestArgs { 69 | lptim3: LpTim3, 70 | rcc: pac::RCC, 71 | b7: Output, 72 | } 73 | 74 | #[init] 75 | fn init() -> TestArgs { 76 | cortex_m::interrupt::free(|cs| { 77 | let mut dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 78 | let mut cp: pac::CorePeripherals = unwrap!(pac::CorePeripherals::take()); 79 | 80 | unsafe { rcc::pulse_reset_backup_domain(&mut dp.RCC, &mut dp.PWR) }; 81 | unsafe { rcc::set_sysclk_msi_max(&mut dp.FLASH, &mut dp.PWR, &mut dp.RCC, cs) }; 82 | defmt::assert_eq!(rcc::sysclk_hz(&dp.RCC), FREQ); 83 | 84 | cp.DCB.enable_trace(); 85 | cp.DWT.enable_cycle_counter(); 86 | cp.DWT.set_cycle_count(0); 87 | 88 | // enable HSI 89 | dp.RCC.cr.modify(|_, w| w.hsion().set_bit()); 90 | while dp.RCC.cr.read().hsirdy().is_not_ready() {} 91 | 92 | // enable LSE 93 | dp.PWR.cr1.modify(|_, w| w.dbp().enabled()); 94 | dp.RCC 95 | .bdcr 96 | .modify(|_, w| w.lseon().on().lsesysen().enabled()); 97 | while dp.RCC.bdcr.read().lserdy().is_not_ready() {} 98 | 99 | // enable LSI 100 | rcc::enable_lsi(&mut dp.RCC); 101 | 102 | let _: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 103 | let gpiob: PortB = PortB::split(dp.GPIOB, &mut dp.RCC); 104 | 105 | let lptim3: LpTim3 = 106 | LpTim3::new(dp.LPTIM3, lptim::Clk::Hsi16, Prescaler::Div1, &mut dp.RCC); 107 | 108 | TestArgs { 109 | lptim3, 110 | rcc: dp.RCC, 111 | b7: Output::default(gpiob.b7, cs), 112 | } 113 | }) 114 | } 115 | 116 | #[test] 117 | fn oneshot_external_trigger(ta: &mut TestArgs) { 118 | defmt::warn!("Pin B7 must be connected to A11 for this test to pass"); 119 | let lptim3_freq: u32 = ta.lptim3.hz().to_integer(); 120 | defmt::assert_eq!(FREQ % lptim3_freq, 0); 121 | 122 | let a11 = unsafe { PortA::steal() }.a11; 123 | 124 | cortex_m::interrupt::free(|cs| { 125 | ta.lptim3 126 | .new_trigger_pin(a11, Filter::Any, TrgPol::Both, cs) 127 | }); 128 | ta.lptim3.start(u16::MAX); 129 | 130 | // wait 10 LPTIM3 cycles 131 | let start: u32 = DWT::cycle_count(); 132 | loop { 133 | let elapsed: u32 = DWT::cycle_count() - start; 134 | if elapsed > (FREQ / lptim3_freq) * 10 { 135 | break; 136 | } 137 | } 138 | 139 | // timer should still read 0 because it has not triggered 140 | defmt::assert_eq!(LpTim3::cnt(), 0); 141 | 142 | // timer should start when this pin toggles 143 | unwrap!(ta.b7.toggle()); 144 | 145 | // wait 10 LPTIM3 cycles 146 | let start: u32 = DWT::cycle_count(); 147 | loop { 148 | let elapsed: u32 = DWT::cycle_count() - start; 149 | if elapsed > (FREQ / lptim3_freq) * 10 { 150 | break; 151 | } 152 | } 153 | 154 | defmt::assert_ne!(LpTim3::cnt(), 0); 155 | } 156 | 157 | #[test] 158 | fn oneshot(ta: &mut TestArgs) { 159 | const SRCS: [lptim::Clk; 4] = [ 160 | lptim::Clk::Hsi16, 161 | lptim::Clk::Lse, 162 | lptim::Clk::Lsi, 163 | lptim::Clk::Pclk, 164 | ]; 165 | const PRESCALERS: [lptim::Prescaler; 8] = [ 166 | lptim::Prescaler::Div1, 167 | lptim::Prescaler::Div2, 168 | lptim::Prescaler::Div4, 169 | lptim::Prescaler::Div8, 170 | lptim::Prescaler::Div16, 171 | lptim::Prescaler::Div32, 172 | lptim::Prescaler::Div64, 173 | lptim::Prescaler::Div128, 174 | ]; 175 | 176 | for (src, pre) in itertools::iproduct!(SRCS, PRESCALERS) { 177 | let dp: pac::Peripherals = unsafe { pac::Peripherals::steal() }; 178 | let mut lptim1: LpTim1 = LpTim1::new(dp.LPTIM1, src, pre, &mut ta.rcc); 179 | let freq: u32 = lptim1.hz().to_integer(); 180 | defmt::info!("LPTIM1 {} / {} = {} Hz", src, pre.div(), freq); 181 | test_clk_src(&mut lptim1, src, &dp.RCC); 182 | } 183 | 184 | for (src, pre) in itertools::iproduct!(SRCS, PRESCALERS) { 185 | let dp: pac::Peripherals = unsafe { pac::Peripherals::steal() }; 186 | let mut lptim2: LpTim2 = LpTim2::new(dp.LPTIM2, src, pre, &mut ta.rcc); 187 | let freq: u32 = lptim2.hz().to_integer(); 188 | defmt::info!("LPTIM2 {} / {} = {} Hz", src, pre.div(), freq); 189 | test_clk_src(&mut lptim2, src, &dp.RCC); 190 | } 191 | 192 | for (src, pre) in itertools::iproduct!(SRCS, PRESCALERS) { 193 | let dp: pac::Peripherals = unsafe { pac::Peripherals::steal() }; 194 | let mut lptim3: LpTim3 = LpTim3::new(dp.LPTIM3, src, pre, &mut ta.rcc); 195 | let freq: u32 = lptim3.hz().to_integer(); 196 | defmt::info!("LPTIM3 {} / {} = {} Hz", src, pre.div(), freq); 197 | test_clk_src(&mut lptim3, src, &dp.RCC); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /testsuite/src/rcc.rs: -------------------------------------------------------------------------------- 1 | // Most of the coverage for SPI comes from the Sub-GHz testsuite. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use defmt::unwrap; 7 | use defmt_rtt as _; // global logger 8 | use itertools::iproduct; 9 | use nucleo_wl55jc_bsp::hal::{ 10 | cortex_m::{self, interrupt::CriticalSection}, 11 | pac, 12 | pwr::{LprunRange, enter_lprun_msi, exit_lprun}, 13 | rcc::{self, LsiPre, MsiRange, Vos, lsi_hz, set_sysclk_msi_max, setup_lsi}, 14 | }; 15 | use panic_probe as _; 16 | 17 | #[derive(defmt::Format)] 18 | pub enum SysClkSrc { 19 | Msi(MsiRange), 20 | Hse(Vos), 21 | Hsi, 22 | } 23 | 24 | impl SysClkSrc { 25 | pub unsafe fn set( 26 | &self, 27 | flash: &mut pac::FLASH, 28 | pwr: &mut pac::PWR, 29 | rcc: &mut pac::RCC, 30 | cs: &CriticalSection, 31 | ) { 32 | unsafe { 33 | match self { 34 | SysClkSrc::Msi(range) => rcc::set_sysclk_msi(flash, pwr, rcc, *range, cs), 35 | SysClkSrc::Hse(vos) => rcc::set_sysclk_hse(flash, pwr, rcc, *vos, cs), 36 | SysClkSrc::Hsi => rcc::set_sysclk_hsi(flash, pwr, rcc, cs), 37 | } 38 | } 39 | } 40 | 41 | pub fn to_hz(&self) -> u32 { 42 | match self { 43 | SysClkSrc::Msi(range) => range.to_hz(), 44 | SysClkSrc::Hse(vos) => match vos { 45 | Vos::V1_2 => 32_000_000, 46 | Vos::V1_0 => 16_000_000, 47 | }, 48 | SysClkSrc::Hsi => 16_000_000, 49 | } 50 | } 51 | } 52 | 53 | const LPRUN_RANGES: [LprunRange; 4] = [ 54 | // STLink drops the connection when switching to 100k 55 | // with the default JTAG clock frequency set by probe-rs 56 | // LprunRange::Range100k, 57 | LprunRange::Range200k, 58 | LprunRange::Range400k, 59 | LprunRange::Range800k, 60 | LprunRange::Range1M, 61 | ]; 62 | 63 | const CLKS: [SysClkSrc; 14] = [ 64 | SysClkSrc::Hsi, 65 | SysClkSrc::Hse(Vos::V1_0), 66 | SysClkSrc::Hse(Vos::V1_2), 67 | // STLink drops the connection when switching to 100k 68 | // with the default JTAG clock frequency set by probe-rs 69 | // SysClkSrc::Msi(MsiRange::Range100k), 70 | SysClkSrc::Msi(MsiRange::Range200k), 71 | SysClkSrc::Msi(MsiRange::Range400k), 72 | SysClkSrc::Msi(MsiRange::Range800k), 73 | SysClkSrc::Msi(MsiRange::Range1M), 74 | SysClkSrc::Msi(MsiRange::Range2M), 75 | SysClkSrc::Msi(MsiRange::Range4M), 76 | SysClkSrc::Msi(MsiRange::Range8M), 77 | SysClkSrc::Msi(MsiRange::Range16M), 78 | SysClkSrc::Msi(MsiRange::Range24M), 79 | SysClkSrc::Msi(MsiRange::Range32M), 80 | SysClkSrc::Msi(MsiRange::Range48M), 81 | ]; 82 | 83 | // HardFault is a symptom of the MSI switching erratum 84 | #[cortex_m_rt::exception] 85 | #[allow(non_snake_case)] 86 | unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { 87 | cortex_m::interrupt::disable(); 88 | defmt::error!("HardFault {:#}", defmt::Debug2Format(ef)); 89 | defmt::flush(); 90 | loop { 91 | cortex_m::asm::udf() 92 | } 93 | } 94 | 95 | #[defmt_test::tests] 96 | mod tests { 97 | use super::*; 98 | 99 | struct TestArgs { 100 | flash: pac::FLASH, 101 | pwr: pac::PWR, 102 | rcc: pac::RCC, 103 | } 104 | 105 | #[init] 106 | fn init() -> TestArgs { 107 | let dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 108 | 109 | TestArgs { 110 | rcc: dp.RCC, 111 | pwr: dp.PWR, 112 | flash: dp.FLASH, 113 | } 114 | } 115 | 116 | // exhaustive tests of sysclk switching from every possible source to every possible source 117 | #[test] 118 | fn sysclk_switching(ta: &mut TestArgs) { 119 | for (from, to) in iproduct!(CLKS.iter(), CLKS.iter()) { 120 | defmt::info!("from {} to {}", from, to); 121 | 122 | cortex_m::interrupt::free(|cs| unsafe { 123 | from.set(&mut ta.flash, &mut ta.pwr, &mut ta.rcc, cs) 124 | }); 125 | defmt::assert_eq!(rcc::sysclk_hz(&ta.rcc), from.to_hz()); 126 | 127 | cortex_m::interrupt::free(|cs| unsafe { 128 | to.set(&mut ta.flash, &mut ta.pwr, &mut ta.rcc, cs) 129 | }); 130 | defmt::assert_eq!(rcc::sysclk_hz(&ta.rcc), to.to_hz()); 131 | } 132 | 133 | cortex_m::interrupt::free(|cs| unsafe { 134 | set_sysclk_msi_max(&mut ta.flash, &mut ta.pwr, &mut ta.rcc, cs) 135 | }); 136 | } 137 | 138 | #[test] 139 | fn enter_exit_lprun(ta: &mut TestArgs) { 140 | for &lprunrange in LPRUN_RANGES.iter() { 141 | defmt::info!("{}", lprunrange); 142 | 143 | cortex_m::interrupt::free(|cs| unsafe { 144 | enter_lprun_msi(&mut ta.flash, &mut ta.pwr, &mut ta.rcc, lprunrange, cs) 145 | }); 146 | cortex_m::interrupt::free(|cs| unsafe { 147 | enter_lprun_msi(&mut ta.flash, &mut ta.pwr, &mut ta.rcc, lprunrange, cs) 148 | }); 149 | 150 | exit_lprun(&mut ta.pwr); 151 | exit_lprun(&mut ta.pwr); 152 | } 153 | 154 | cortex_m::interrupt::free(|cs| unsafe { 155 | set_sysclk_msi_max(&mut ta.flash, &mut ta.pwr, &mut ta.rcc, cs) 156 | }); 157 | } 158 | 159 | #[test] 160 | fn lsi_to_from(ta: &mut TestArgs) { 161 | #[derive(defmt::Format, Clone, Copy)] 162 | enum TestPre { 163 | Div1, 164 | Div128, 165 | } 166 | impl TestPre { 167 | const fn hz(&self) -> u16 { 168 | match self { 169 | TestPre::Div1 => 32_000, 170 | TestPre::Div128 => 250, 171 | } 172 | } 173 | } 174 | impl From<&TestPre> for LsiPre { 175 | fn from(tp: &TestPre) -> Self { 176 | match tp { 177 | TestPre::Div1 => LsiPre::Div1, 178 | TestPre::Div128 => LsiPre::Div128, 179 | } 180 | } 181 | } 182 | 183 | const LSI_PRESCALERS: [TestPre; 2] = [TestPre::Div1, TestPre::Div128]; 184 | 185 | for (from, to) in iproduct!(LSI_PRESCALERS.iter(), LSI_PRESCALERS.iter()) { 186 | defmt::info!("LSI disabled from {} to {}", from, to); 187 | 188 | ta.rcc.csr.modify(|_, w| w.lsion().off()); 189 | unsafe { setup_lsi(&mut ta.rcc, from.into()) }; 190 | assert_eq!(lsi_hz(&ta.rcc), from.hz()); 191 | 192 | ta.rcc.csr.modify(|_, w| w.lsion().off()); 193 | unsafe { setup_lsi(&mut ta.rcc, to.into()) }; 194 | assert_eq!(lsi_hz(&ta.rcc), to.hz()); 195 | } 196 | 197 | for (from, to) in iproduct!(LSI_PRESCALERS.iter(), LSI_PRESCALERS.iter()) { 198 | defmt::info!("LSI enabled from {} to {}", from, to); 199 | 200 | unsafe { setup_lsi(&mut ta.rcc, from.into()) }; 201 | assert_eq!(lsi_hz(&ta.rcc), from.hz()); 202 | 203 | unsafe { setup_lsi(&mut ta.rcc, to.into()) }; 204 | assert_eq!(lsi_hz(&ta.rcc), to.hz()); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /testsuite/src/rng.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use defmt_rtt as _; // global logger 6 | use nucleo_wl55jc_bsp::hal::{ 7 | cortex_m, pac, rcc, 8 | rng::{Clk, Rng, rand_core::TryRngCore as _}, 9 | }; 10 | use panic_probe as _; 11 | 12 | /// This is not a cryptographically secure validation, this only ensures that 13 | /// the driver is operating nominally, is does not check for hardware 14 | /// correctness. 15 | fn validate_randomness(entropy: &[u8]) { 16 | let mut sum: f64 = 0.0; 17 | for byte in entropy.iter() { 18 | sum += *byte as f64; 19 | } 20 | 21 | sum /= entropy.len() as f64; 22 | 23 | const MID: f64 = 127.5; 24 | const TOLERANCE: f64 = MID / 2.0; 25 | const LO: f64 = MID - TOLERANCE; 26 | const HI: f64 = MID + TOLERANCE; 27 | 28 | defmt::assert!(sum > LO && sum < HI, "{:?}", sum); 29 | } 30 | 31 | #[defmt_test::tests] 32 | mod tests { 33 | use super::*; 34 | 35 | #[init] 36 | fn init() -> Rng { 37 | let mut dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 38 | 39 | cortex_m::interrupt::free(|cs| unsafe { 40 | rcc::set_sysclk_msi_max(&mut dp.FLASH, &mut dp.PWR, &mut dp.RCC, cs) 41 | }); 42 | 43 | Rng::new(dp.RNG, Clk::Msi, &mut dp.RCC) 44 | } 45 | 46 | /// Not really a test, just a benchmark 47 | #[test] 48 | fn cha_cha(rng: &mut Rng) { 49 | defmt::warn!("Test results are only valid for release mode"); 50 | use rand_chacha::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng, rand_core::SeedableRng}; 51 | 52 | let mut seed: [u8; 32] = [0; 32]; 53 | 54 | unwrap!(rng.try_fill_u8(&mut seed)); 55 | let mut cha20: ChaCha20Rng = ChaCha20Rng::from_seed(seed); 56 | unwrap!(rng.try_fill_u8(&mut seed)); 57 | let mut cha12: ChaCha12Rng = ChaCha12Rng::from_seed(seed); 58 | unwrap!(rng.try_fill_u8(&mut seed)); 59 | let mut cha8: ChaCha8Rng = ChaCha8Rng::from_seed(seed); 60 | 61 | let mut cp = unwrap!(pac::CorePeripherals::take()); 62 | cp.DCB.enable_trace(); 63 | cp.DWT.enable_cycle_counter(); 64 | 65 | const NUM_BLK: usize = 200; 66 | const BUF_SIZE: usize = NUM_BLK * 4 * 4; 67 | static mut BUF: [u8; BUF_SIZE] = [0; BUF_SIZE]; 68 | 69 | let start: u32 = pac::DWT::cycle_count(); 70 | #[allow(static_mut_refs)] 71 | let ret = cha20.try_fill_bytes(unsafe { &mut BUF }); 72 | let end: u32 = pac::DWT::cycle_count(); 73 | defmt::assert!(ret.is_ok()); 74 | let cha20cyc: u32 = end.wrapping_sub(start); 75 | 76 | let start: u32 = pac::DWT::cycle_count(); 77 | #[allow(static_mut_refs)] 78 | let ret = cha12.try_fill_bytes(unsafe { &mut BUF }); 79 | let end: u32 = pac::DWT::cycle_count(); 80 | defmt::assert!(ret.is_ok()); 81 | let cha12cyc: u32 = end.wrapping_sub(start); 82 | 83 | let start: u32 = pac::DWT::cycle_count(); 84 | #[allow(static_mut_refs)] 85 | let ret = cha8.try_fill_bytes(unsafe { &mut BUF }); 86 | let end: u32 = pac::DWT::cycle_count(); 87 | defmt::assert!(ret.is_ok()); 88 | let cha8cyc: u32 = end.wrapping_sub(start); 89 | 90 | let start: u32 = pac::DWT::cycle_count(); 91 | #[allow(static_mut_refs)] 92 | let ret = rng.try_fill_bytes(unsafe { &mut BUF }); 93 | let end: u32 = pac::DWT::cycle_count(); 94 | defmt::assert!(ret.is_ok()); 95 | let rngcyc: u32 = end.wrapping_sub(start); 96 | 97 | defmt::info!("ChaCha20: {}", (cha20cyc as f32) / (NUM_BLK as f32)); 98 | defmt::info!("ChaCha12: {}", (cha12cyc as f32) / (NUM_BLK as f32)); 99 | defmt::info!("ChaCha8: {}", (cha8cyc as f32) / (NUM_BLK as f32)); 100 | defmt::info!("HW: {}", (rngcyc as f32) / (NUM_BLK as f32)); 101 | } 102 | 103 | #[test] 104 | fn random_enough_for_me(rng: &mut Rng) { 105 | let mut bytes: [u8; 35] = [0; 35]; 106 | unwrap!(rng.try_fill_u8(&mut bytes)); 107 | validate_randomness(&bytes) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /testsuite/src/uart.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::fmt::Write; 5 | use defmt::unwrap; 6 | use defmt_rtt as _; // global logger 7 | use nucleo_wl55jc_bsp::hal::{ 8 | cortex_m, 9 | dma::{AllDma, Dma1Ch3, Dma2Ch6}, 10 | embedded_hal::prelude::*, 11 | gpio::{PortA, PortC, pins}, 12 | pac, rcc, 13 | uart::{self, LpUart, Uart1}, 14 | }; 15 | use panic_probe as _; 16 | 17 | #[defmt_test::tests] 18 | mod tests { 19 | use super::*; 20 | 21 | struct TestArgs { 22 | lpuart: LpUart<(pins::C0, Dma2Ch6), pins::C1>, 23 | uart1: Uart1, 24 | } 25 | 26 | #[init] 27 | fn init() -> TestArgs { 28 | cortex_m::interrupt::free(|cs| { 29 | let mut dp: pac::Peripherals = unwrap!(pac::Peripherals::take()); 30 | unsafe { rcc::set_sysclk_msi_max(&mut dp.FLASH, &mut dp.PWR, &mut dp.RCC, cs) }; 31 | 32 | dp.RCC.cr.modify(|_, w| w.hsion().set_bit()); 33 | while dp.RCC.cr.read().hsirdy().is_not_ready() {} 34 | 35 | let dma: AllDma = AllDma::split(dp.DMAMUX, dp.DMA1, dp.DMA2, &mut dp.RCC); 36 | let gpioa: PortA = PortA::split(dp.GPIOA, &mut dp.RCC); 37 | let gpioc: PortC = PortC::split(dp.GPIOC, &mut dp.RCC); 38 | 39 | let lpuart: LpUart<(pins::C0, Dma2Ch6), pins::C1> = 40 | LpUart::new(dp.LPUART, 115200, uart::Clk::Hsi16, &mut dp.RCC) 41 | .enable_rx_dma(gpioc.c0, dma.d2.c6, cs) 42 | .enable_tx(gpioc.c1, cs); 43 | let uart1: Uart1 = 44 | Uart1::new(dp.USART1, 115200, uart::Clk::Hsi16, &mut dp.RCC) 45 | .enable_rx(gpioa.a10, cs) 46 | .enable_tx_dma(gpioa.a9, dma.d1.c3, cs); 47 | 48 | defmt::warn!( 49 | "UART tests require C1 (LPUART TX) connected to A10 (UART1 RX) and \ 50 | C0 (LPUART RX) connected to A9 (UART1 TX)" 51 | ); 52 | 53 | TestArgs { lpuart, uart1 } 54 | }) 55 | } 56 | 57 | #[test] 58 | fn single_byte_loopback_no_dma(ta: &mut TestArgs) { 59 | const WORD: u8 = 0xAA; 60 | unwrap!(nb::block!(ta.lpuart.write(WORD))); 61 | let out: u8 = unwrap!(nb::block!(ta.uart1.read())); 62 | 63 | defmt::assert_eq!(WORD, out); 64 | } 65 | 66 | #[test] 67 | fn single_byte_loopback_with_dma(ta: &mut TestArgs) { 68 | const WORD: u8 = 0x55; 69 | unwrap!(ta.uart1.bwrite_all(&[WORD])); 70 | let mut read_buf: [u8; 1] = [0]; 71 | unwrap!(ta.lpuart.bread_all(&mut read_buf)); 72 | 73 | defmt::assert_eq!(WORD, read_buf[0]); 74 | } 75 | 76 | #[test] 77 | fn core_fmt(ta: &mut TestArgs) { 78 | const EXPECTED: &str = "Hello, world!\n"; 79 | unwrap!(write!(&mut ta.lpuart, "Hello, {}!\n", "world").ok()); 80 | for &expected_byte in EXPECTED.as_bytes() { 81 | let rx_byte: u8 = unwrap!(nb::block!(ta.uart1.read())); 82 | defmt::assert_eq!(rx_byte, expected_byte); 83 | } 84 | } 85 | } 86 | --------------------------------------------------------------------------------