diff --git a/src/bin/minimal.rs b/src/bin/minimal.rs index b3212e1..d0215ae 100644 --- a/src/bin/minimal.rs +++ b/src/bin/minimal.rs @@ -182,10 +182,14 @@ mod app { #[task(binds = USART1, shared = [gps])] fn on_uart(mut cx: on_uart::Context) { - info!("hewwo"); - cx.shared.gps.lock(|gps| { - gps.handle(); - }); + info!("foo"); + if let Ok(b) = cx.shared.gps.lock(|gps| gps.serial.read()) { + info!("hewwo {}", b); + } + // cx.shared.gps.lock(|gps| { + // gps.handle(); + // }); + } #[task(binds = RTC_WKUP)] diff --git a/stm32l4xx-hal b/stm32l4xx-hal deleted file mode 160000 index 26e0917..0000000 --- a/stm32l4xx-hal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 26e0917e782402251e3eeaef53f1adb4fad22a42 diff --git a/stm32l4xx-hal/.cargo/config b/stm32l4xx-hal/.cargo/config new file mode 100644 index 0000000..18401e9 --- /dev/null +++ b/stm32l4xx-hal/.cargo/config @@ -0,0 +1,31 @@ +#[target.thumbv7m-none-eabi] +# uncomment this to make `cargo run` execute programs on QEMU +# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# uncomment ONE of these three option to make `cargo run` start a GDB session +# which option to pick depends on your system +runner = "arm-none-eabi-gdb -q -x openocd.gdb" +# runner = "gdb-multiarch -q -x openocd.gdb" +# runner = "gdb -q -x openocd.gdb" + +rustflags = [ + # LLD (shipped with the Rust toolchain) is used as the default linker + "-C", "link-arg=-Tlink.x", + + # if you run into problems with LLD switch to the GNU linker by commenting out + # this line + # "-C", "linker=arm-none-eabi-ld", + + # if you need to link to pre-compiled C libraries provided by a C toolchain + # use GCC as the linker by commenting out both lines above and then + # uncommenting the three lines below + # "-C", "linker=arm-none-eabi-gcc", + # "-C", "link-arg=-Wl,-Tlink.x", + # "-C", "link-arg=-nostartfiles", +] + +[build] +# Pick ONE of these compilation targets +# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) diff --git a/stm32l4xx-hal/.github/workflows/ci.yml b/stm32l4xx-hal/.github/workflows/ci.yml new file mode 100644 index 0000000..84357e1 --- /dev/null +++ b/stm32l4xx-hal/.github/workflows/ci.yml @@ -0,0 +1,82 @@ +on: + push: + branches: [master] + pull_request: + +name: Continuous integration + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: # All permutations of {rust, mcu} + rust: + - stable + mcu: # Note leading comma is required if any additional fetures are specified + - { id: stm32l412, additional-features: ",stm32-usbd" } + - { id: stm32l422, additional-features: ",stm32-usbd" } + - { id: stm32l431, additional-features: "" } + - { id: stm32l432, additional-features: ",stm32-usbd" } + - { id: stm32l433, additional-features: ",stm32-usbd" } + - { id: stm32l442, additional-features: ",stm32-usbd" } + - { id: stm32l443, additional-features: ",stm32-usbd" } + - { id: stm32l451, additional-features: "" } + - { id: stm32l452, additional-features: ",stm32-usbd" } + - { id: stm32l462, additional-features: ",stm32-usbd" } + - { id: stm32l471, additional-features: "" } + - { id: stm32l475, additional-features: "" } # USB_OTG not supported by PAC + - { id: stm32l476, additional-features: ",otg_fs" } + - { id: stm32l486, additional-features: ",otg_fs" } + - { id: stm32l496, additional-features: ",otg_fs" } + - { id: stm32l4a6, additional-features: ",otg_fs" } + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: thumbv7em-none-eabihf + override: true + - name: build + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --verbose --release --examples --target thumbv7em-none-eabihf --features rt,unproven,${{ matrix.mcu.id }}${{ matrix.mcu.additional-features }} + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --target x86_64-unknown-linux-gnu --features rt,unproven,${{ matrix.mcu.id }}${{ matrix.mcu.additional-features }} + + ci-r9: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + mcu: + - { id: stm32l4r9, additional-features: "" } + - { id: stm32l4s9, additional-features: "" } + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: thumbv7em-none-eabihf + override: true + - name: build + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --verbose --release --target thumbv7em-none-eabihf --features rt,unproven,${{ matrix.mcu.id }}${{ matrix.mcu.additional-features }} + # note that examples were not built + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --target x86_64-unknown-linux-gnu --features rt,unproven,${{ matrix.mcu.id }}${{ matrix.mcu.additional-features }} diff --git a/stm32l4xx-hal/.github/workflows/clippy.yml b/stm32l4xx-hal/.github/workflows/clippy.yml new file mode 100644 index 0000000..8cf4825 --- /dev/null +++ b/stm32l4xx-hal/.github/workflows/clippy.yml @@ -0,0 +1,22 @@ +on: + push: + branches: [ master ] + pull_request: + +name: Clippy check +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + override: true + components: clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --examples --target thumbv7em-none-eabihf --features=stm32l432,rt,unproven diff --git a/stm32l4xx-hal/.github/workflows/rustfmt.yml b/stm32l4xx-hal/.github/workflows/rustfmt.yml new file mode 100644 index 0000000..102f0e3 --- /dev/null +++ b/stm32l4xx-hal/.github/workflows/rustfmt.yml @@ -0,0 +1,23 @@ +on: + push: + branches: [ master ] + pull_request: + +name: Code formatting check + +jobs: + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/stm32l4xx-hal/.gitignore b/stm32l4xx-hal/.gitignore new file mode 100644 index 0000000..4f80244 --- /dev/null +++ b/stm32l4xx-hal/.gitignore @@ -0,0 +1,10 @@ +/target +**/*.rs.bk +Cargo.lock + +# cargo-embed +/Embed.local.toml +/logs + +# Visual Studio Code +/.vscode diff --git a/stm32l4xx-hal/CHANGELOG.md b/stm32l4xx-hal/CHANGELOG.md new file mode 100644 index 0000000..20de7b7 --- /dev/null +++ b/stm32l4xx-hal/CHANGELOG.md @@ -0,0 +1,271 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + + +## [v0.7.1] - 2022-04-11 + +### Fixed + + - Shorten old buffer when we have extra characters in DMA buffer after a character match. +## [v0.7.0] - 2022-04-04 + +### Added + + - Add delay implementation based on `cortex_m::asm::delay`. + - Implement `RngCore` and `CryptoRng` for the hardware RNG. + - Support analog to digital converters (ADC). + - Support SPI slave mode. + - Add DMA and interrupt support for SPI and ADC peripherals. + - Add support for measuring Vref, Vbat and temperature. + - Add alternate function 0 to GPIO. + - Add preliminary bxCAN support. + - Add more GPIO combinations for I2C and PWM peripherals. + - Add wakeup clock sources to RTC config. + - Support RTC domain backup registers. + - Support I2C on stm32l4x3 devices. + - Support I2C3 peripheral on stm32l4x6 devices. + - Experimental support for the Synopsis USB library. + - Experimental support for USB OTG FS for stm32l4x5 and stm32l4x6 devices. + - Support stm32l4r9 devices. + +### Changed + + - Use device-specific features rather than by-peripheral features. + - Use `fugit` duration nd rate units instead of custom + - Use const-generics for GPIO (require Rust 1.51) + - Import I2C implementation from `stm32h7xx-hal` crate. + - Use a `Config` struct for initializing I2C peripherals. + - Check that the clock requested for the low-power timer is enabled. + - Take `clocks` argument by value when setting up low-power timer. + - Use sane low-power timer defaults (LSI, no prescaler). + - Make `LowPowerTimer<_>::set_autoreload()` public. + - Enable SPI2 for all stm32l4x2 devices as some of them have it. + - Target hardfp by default since stm32l4 cores are Cortex-M4F. + - Require typed input when converting from milliseconds to hertz. + - Rework alternate function typestates. + - Use MSI as default/fallback clock source for SYSCLK. + - Use specialized PAC for stm32l412 and stm32l422 devices. + - Add `toggeable` trait to GPIO pins. + - Update `stm32l4` dependency. + +### Fixed + + - Fix TIM1 PWM frequency computation. + - Fix TIM5 counter width. + - Fix PSC computation off-by-one error. + - Change wait states values according to datasheet. + - Fix incorrect I2C2 on PC0/PC1 on stm32l4x3 devices. + - Swap QSPI pins and remove conflicting/wrong implementations. + - Add power on GPIOG pins. + - Support 0 byte writes on I2C. + +## [v0.6.0] - 2020-12-11 + +### Added + + - USB driver for `stm32l4x2` and `stm32l4x3` devices. + - USART3 and UART4, with dma support. + - Support for GPIOF and GPIOG. + - USB driver examples. + - Support for external pin interrupts (EXTI). + - Implement hardware flow control for serial. + - Support for CRC peripheral. + - Implement `core::hash::Hasher` for the CRC peripheral. + - Support for serial framing. + - Support for Half-Duplex Serial. + - Support for timers 5 and 6. + - Bump stm32l4 to `0.11.0`. + - Support for QSPI. + - Support for RTC alarms. + - Support for the Independant Watch Dog Timer. + - VSCode configurations. + - Added signature module. + + +## Changed + - Reworked clock control with RCC. + - Rework the DMA API. + - Rework the FLASH programming API. + - Rework GPIO output to support setting Speed, Pullups and drain functionality. + - Switched to Github actions. + +### Fixed + - Re-add support for GPIOD & GPIOE. + - Fix enable bits for PWM. + - Fix clearing serial error flag bits. + - I2C 7bit adressing shift. + - Fix the systick delay to support delays greater than `1 << 24` + - Clean up examples, and Deps. + +## [v0.5.0] - 2019-09-02 + +### Added + + - DMA2 + +### Breaking + + - Serial ports now use a config, instead of just the baudrate. + - Upgraded embedded-hal, gpio method signitures are now fallible. + - Bumped cortex-m. + + +## [v0.4.0] - 2019-05-08 + +### Added + + - PWM support for select timers + - More GPIO support + +### Fixed + + - More debug and Eq derives where appropriate + - Updated to stm32-rs v0.7.0 which fixes [#32](https://github.com/stm32-rs/stm32l4xx-hal/issues/32) for stm32l4x5 and stm32l4x6 devices + +## [v0.3.6] - 2019-02-11 + +### Added + + - GPIOE Support + - MSI Clock support + +### Fixed + + - The TSC `aquire` did not reset the channel select register after aquisition + - The default charge high and charge low time for the TSC were fixed at the max, this now has a sane default and can be configured with the TSC config. + - HCLK and PCKL rcc prescaling, although setting the right clocks, the value in `Clocks` was wrong, this now correctly calculated. + + +## [v0.3.5] - 2019-01-07 + +### Added + + - Rng implementation + - GPIOE support + - `fmt::Write` for serial Tx + +### Breaking + - LSI is no longer enabled by default, requires `lsi(true)` when configuring the rcc + +## [v0.3.4] - 2018-12-31 + +### Fixed + - Hardcoded stm32 device crate in `i2c` + +### Added + - TSC clear flag API + +## [v0.3.3] - 2018-12-12 + +### Fixed + - Bumped stm32l4 to v0.5.0 + - Fixed timer start macro + +## [v0.3.2] - 2018-12-12 + +### Added + - DMA buffers now use as-slice + +### Fixed + - This crate now compiles on stable (1.31)! + +## [v0.3.1] - 2018-12-10 + +### Added + - I2C support using embedded-hal traits + - Added GPIO input embedded-hal traits + +## [v0.3.0] - 2018-11-21 + +### Breaking + - Move device selection (l4x1, l4x2, etc.) behind feature, in line with stm32f4xx-hal. + - Change crate name accordingly to `stm32l4-hal`. + +### Fixed + - Update crate dependencies. + +## [v0.2.7] - 2018-11-21 + +### Breaking + - EOL the stm32l432xx-hal crate + +## [v0.2.6] - 2018-11-03 + +## Fixed + + - TSC flags not getting cleared by the `aquire` tsc method. + +## [v0.2.5] - YANKED + +## [v0.2.4] - 2018-10-32 + +### Fixed + - Updated examples and bumped dependancies. + +## [v0.2.3] - 2018-10-06 + +### Fixed + - RCC configuration for higher clocks, using the wrong multiplyier reg caused issue with time sensative peripherals + +### Added + - New TSC Configuration, to set the prescaler and max count value. + +## [v0.2.2] - 2018-08-25 + - Fixed documentation link in readme. + +## [v0.2.1] - 2018-08-25 + +### Added + - Timer implementations for timer15 & timer 16 + - Two new TSC API's, `read` & `read_unchecked` + - `read` checks the supplied pin is the pin we are reading + - `read_unchecked` returns the contents of the count register + +### Fixed + - Channel pins do not require Schmitt trigger hysteresis + +## [v0.2.0] - 2018-08-21 + +### Breaking + - GPIO type signature changed to allow pin state and alterante function to be encoded in the pin type. + +### Added + - Touch sense controller peripheral support + - Easy to use blocking API with interrupt support too + +## [v0.1.1] - 2018-08-13 + +### Added + - Idle line detection for Usart Rx + - Idle Line Interrupt listening + +### Fixed + - DMA Circular peeking, now correctly resets `consumed_offset` + +## v0.1.0 - 2018-08-05 + +- Initial release + +[v0.7.0]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.6.0...v0.7.0 +[v0.6.0]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.5.0...v0.6.0 +[v0.5.0]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.4.0...v0.5.0 +[v0.4.0]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.6...v0.4.0 +[v0.3.6]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.5...v0.3.6 +[v0.3.5]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.4...v0.3.5 +[v0.3.4]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.3...v0.3.4 +[v0.3.3]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.2...v0.3.3 +[v0.3.2]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.1...v0.3.2 +[v0.3.1]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.3.0...v0.3.1 +[v0.3.0]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.7...v0.3.0 +[v0.2.7]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.6...v0.2.7 +[v0.2.6]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.5...v0.2.6 +[v0.2.5]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.4...v0.2.5 +[v0.2.4]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.3...v0.2.4 +[v0.2.3]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.2...v0.2.3 +[v0.2.2]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.1...v0.2.2 +[v0.2.1]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/stm32-rs/stm32l4xx-hal/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/stm32-rs/stm32l4xx-hal/tree/v0.1.0 diff --git a/stm32l4xx-hal/Cargo.toml b/stm32l4xx-hal/Cargo.toml new file mode 100644 index 0000000..31e1ad8 --- /dev/null +++ b/stm32l4xx-hal/Cargo.toml @@ -0,0 +1,200 @@ +[package] +name = "stm32l4xx-hal" +version = "0.7.1" +authors = ["Scott Mabin "] +description = "Hardware abstraction layer for the stm32l4xx chips" +keywords = ["no-std", "stm32l4xx", "stm32l4", "embedded", "embedded-hal"] +categories = [ + "embedded", + "hardware-support", + "no-std", +] +repository = "https://github.com/MabezDev/stm32l4xx-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +exclude = [ + ".travis.yml", + ".gitignore", + "docs/", + "docs/*" +] +edition = "2018" + +[dependencies] +cortex-m = "0.7" +nb = "0.1.1" +stm32l4 = "0.14.0" +embedded-dma = "0.1" +bxcan = ">=0.4, <0.7" +fugit = "0.3.5" + +[dependencies.rand_core] +version = "0.6.2" +default-features = false + +[dependencies.cast] +version = "0.2.2" +default-features = false + +[dependencies.void] +version = "1.0.2" +default-features = false + +[dependencies.stable_deref_trait] +default-features = false +version = "1.1" + +[dependencies.embedded-hal] +version = "0.2.6" +features = ["unproven"] + +[dependencies.stm32-usbd] +version = "0.6.0" +optional = true + +[dependencies.synopsys-usb-otg] +version = "0.2.4" +features = ["cortex-m", "fs"] +optional = true + +[package.metadata.docs.rs] +features = ["rt", "stm32l432", "stm32-usbd"] + +[features] +rt = ["stm32l4/rt"] +unproven = ["embedded-hal/unproven"] +otg_fs = ["synopsys-usb-otg"] + +# L4x1 +stm32l431 = [ "stm32l4/stm32l4x1" ] +stm32l451 = [ "stm32l4/stm32l4x1" ] +stm32l471 = [ "stm32l4/stm32l4x1" ] + +# L412 +stm32l412 = [ "stm32l4/stm32l412" ] +stm32l422 = [ "stm32l4/stm32l412" ] + +# L4x2 +stm32l432 = [ "stm32l4/stm32l4x2" ] +stm32l442 = [ "stm32l4/stm32l4x2" ] +stm32l452 = [ "stm32l4/stm32l4x2" ] +stm32l462 = [ "stm32l4/stm32l4x2" ] + +# L4x3 +stm32l433 = [ "stm32l4/stm32l4x3" ] +stm32l443 = [ "stm32l4/stm32l4x3" ] + +# L4x5 +stm32l475 = [ "stm32l4/stm32l4x5" ] + +# L4x6 +stm32l476 = [ "stm32l4/stm32l4x6" ] +stm32l486 = [ "stm32l4/stm32l4x6" ] +stm32l496 = [ "stm32l4/stm32l4x6" ] +stm32l4a6 = [ "stm32l4/stm32l4x6" ] + +# L4+ series PAC support?? +#stm32l4p5 = [ "stm32l4/stm32l4x5" ] +#stm32l4q5 = [ "stm32l4/stm32l4x5" ] +#stm32l4r5 = [ "stm32l4/stm32l4x5" ] +#stm32l4s5 = [ "stm32l4/stm32l4x5" ] + +# L4x7 +#stm32l4r7 = [ "stm32l4/stm32l4x7" ] +#stm32l4s7 = [ "stm32l4/stm32l4x7" ] + +## L4x9 +stm32l4r9 = [ "stm32l4/stm32l4r9" ] # PAC has an L4r9 specific variation +stm32l4s9 = [ "stm32l4/stm32l4r9" ] + +[dev-dependencies] +panic-halt = "0.2.0" +panic-semihosting = "0.5.0" +cortex-m-semihosting = "0.3.5" +cortex-m-rt = "0.7" +usb-device = "0.2.3" +usbd-serial = "0.1.0" +heapless = "0.5" + +[dev-dependencies.cortex-m-rtic] +version = "0.5.9" +default-features = false +features = ["cortex-m-7"] + +[dev-dependencies.panic-rtt-target] +version = "0.1.1" +features = ["cortex-m"] + +[dev-dependencies.rtt-target] +version = "0.2.2" +features = ["cortex-m"] + +[profile.dev] +incremental = false +codegen-units = 1 + +[profile.release] +codegen-units = 1 +debug = true +lto = true + + +[[example]] +name = "adc" +required-features = ["rt"] + +[[example]] +name = "can-loopback" +required-features = ["rt", "stm32l433" ] # CAN peripheral on ever series except L41/L42, gate so CI will pass + +[[example]] +name = "irq_button" +required-features = ["rt"] + +[[example]] +name = "qspi" +required-features = ["rt", "stm32l476"] # L433/43 have no QSPI peripheral and are highly likely to be used + +[[example]] +name = "rng" +required-features = ["unproven"] + +[[example]] +name = "rtc_alarm" +required-features = ["rt"] + +[[example]] +name = "rtic_frame_serial_dma" +required-features = ["rt"] + +[[example]] +name = "spi_dma_rxtx" +required-features = ["rt"] + +[[example]] +name = "serial_echo_rtic" +required-features = ["rt"] + +[[example]] +name = "timer" +required-features = ["rt"] + +[[example]] +name = "usb_serial" +required-features = ["rt", "stm32-usbd"] + +[[example]] +name = "otg_fs_serial" +required-features = ["rt", "otg_fs"] + +[[example]] +name = "i2c_write" +required-features = ["stm32l433"] + +[[example]] +name = "lptim_rtic" +required-features = ["rt"] + +[[example]] +name = "adc_dma" +required-features = ["rt"] diff --git a/stm32l4xx-hal/README.md b/stm32l4xx-hal/README.md new file mode 100644 index 0000000..f0eeb00 --- /dev/null +++ b/stm32l4xx-hal/README.md @@ -0,0 +1,33 @@ +# `stm32l4xx-hal` + +_formerly [MabezDev/stm32l4xx-hal](https://github.com/mabezdev/stm32l4xx-hal)_ + +> [HAL] for the STM32L4xx family of microcontrollers + +- *Note: this HAL is a work in progress, contributions are appreciated :). If you have a L4 device that is currently unsupported see [#29](https://github.com/stm32-rs/stm32l4xx-hal/issues/29)* + +[HAL]: https://crates.io/crates/embedded-hal + +## [Documentation](https://docs.rs/stm32l4xx-hal/latest/stm32l4xx_hal/) + +## [Changelog](https://github.com/mabezdev/stm32l4xx-hal/blob/master/CHANGELOG.md) + +## About + + - Minimum rustc version 1.51 + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/stm32l4xx-hal/docs/nucleo_l432kc_pinout.png b/stm32l4xx-hal/docs/nucleo_l432kc_pinout.png new file mode 100644 index 0000000..5415b3a Binary files /dev/null and b/stm32l4xx-hal/docs/nucleo_l432kc_pinout.png differ diff --git a/stm32l4xx-hal/docs/stm32l432kc.pdf b/stm32l4xx-hal/docs/stm32l432kc.pdf new file mode 100644 index 0000000..effcb3e Binary files /dev/null and b/stm32l4xx-hal/docs/stm32l432kc.pdf differ diff --git a/stm32l4xx-hal/dotgdbinit b/stm32l4xx-hal/dotgdbinit new file mode 100644 index 0000000..a2f186f --- /dev/null +++ b/stm32l4xx-hal/dotgdbinit @@ -0,0 +1,21 @@ +target remote :3333 + +# print demangled symbols by default +set print asm-demangle on + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.fifo uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load +step \ No newline at end of file diff --git a/stm32l4xx-hal/examples/adc.rs b/stm32l4xx-hal/examples/adc.rs new file mode 100644 index 0000000..8b1f821 --- /dev/null +++ b/stm32l4xx-hal/examples/adc.rs @@ -0,0 +1,42 @@ +#![no_main] +#![no_std] + +use panic_rtt_target as _; + +use cortex_m_rt::entry; +use rtt_target::{rprint, rprintln}; +use stm32l4xx_hal::{adc::ADC, delay::Delay, pac, prelude::*}; + +#[entry] +fn main() -> ! { + rtt_target::rtt_init_print!(); + rprint!("Initializing..."); + + let cp = pac::CorePeripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut flash = dp.FLASH.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + let mut delay = Delay::new(cp.SYST, clocks); + let mut adc = ADC::new( + dp.ADC1, + dp.ADC_COMMON, + &mut rcc.ahb2, + &mut rcc.ccipr, + &mut delay, + ); + + let mut gpioc = dp.GPIOC.split(&mut rcc.ahb2); + let mut a1 = gpioc.pc0.into_analog(&mut gpioc.moder, &mut gpioc.pupdr); + + rprintln!(" done."); + + loop { + let value = adc.read(&mut a1).unwrap(); + rprintln!("Value: {}", value); + } +} diff --git a/stm32l4xx-hal/examples/adc_dma.rs b/stm32l4xx-hal/examples/adc_dma.rs new file mode 100644 index 0000000..8e9d801 --- /dev/null +++ b/stm32l4xx-hal/examples/adc_dma.rs @@ -0,0 +1,102 @@ +#![no_main] +#![no_std] + +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use stm32l4xx_hal::{ + adc::{DmaMode, SampleTime, Sequence, ADC}, + delay::DelayCM, + dma::{dma1, RxDma, Transfer, W}, + prelude::*, +}; + +use rtic::app; + +const SEQUENCE_LEN: usize = 3; + +#[app(device = stm32l4xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +const APP: () = { + // RTIC app is written in here! + + struct Resources { + transfer: Option>>, + } + + #[init] + fn init(cx: init::Context) -> init::LateResources { + let MEMORY = { + static mut MEMORY: [u16; SEQUENCE_LEN] = [0u16; SEQUENCE_LEN]; + unsafe { &mut MEMORY } + }; + + rtt_init_print!(); + + rprintln!("Hello from init!"); + + let cp = cx.core; + let mut dcb = cp.DCB; + let mut dwt = cp.DWT; + + dcb.enable_trace(); + dwt.enable_cycle_counter(); + + let pac = cx.device; + + let mut rcc = pac.RCC.constrain(); + let mut flash = pac.FLASH.constrain(); + let mut pwr = pac.PWR.constrain(&mut rcc.apb1r1); + let dma_channels = pac.DMA1.split(&mut rcc.ahb1); + + // + // Initialize the clocks + // + let clocks = rcc.cfgr.sysclk(80.MHz()).freeze(&mut flash.acr, &mut pwr); + + let mut delay = DelayCM::new(clocks); + + let mut adc = ADC::new( + pac.ADC1, + pac.ADC_COMMON, + &mut rcc.ahb2, + &mut rcc.ccipr, + &mut delay, + ); + + let mut temp_pin = adc.enable_temperature(&mut delay); + + let dma1_channel = dma_channels.1; + + adc.configure_sequence(&mut temp_pin, Sequence::One, SampleTime::Cycles12_5); + adc.configure_sequence(&mut temp_pin, Sequence::Two, SampleTime::Cycles247_5); + adc.configure_sequence(&mut temp_pin, Sequence::Three, SampleTime::Cycles640_5); + + // Heapless boxes also work very well as buffers for DMA transfers + let transfer = Transfer::from_adc(adc, dma1_channel, MEMORY, DmaMode::Oneshot, true); + + init::LateResources { + transfer: Some(transfer), + } + } + + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = DMA1_CH1, resources = [transfer])] + fn dma1_interrupt(cx: dma1_interrupt::Context) { + let transfer = cx.resources.transfer; + if let Some(transfer_val) = transfer.take() { + let (buffer, rx_dma) = transfer_val.wait(); + rprintln!("DMA measurements: {:?}", buffer); + *transfer = Some(Transfer::from_adc_dma( + rx_dma, + buffer, + DmaMode::Oneshot, + true, + )); + } + } +}; diff --git a/stm32l4xx-hal/examples/blinky.rs b/stm32l4xx-hal/examples/blinky.rs new file mode 100644 index 0000000..f7faf35 --- /dev/null +++ b/stm32l4xx-hal/examples/blinky.rs @@ -0,0 +1,65 @@ +//! Blinks an LED + +#![no_std] +#![no_main] + +extern crate cortex_m; +#[macro_use] +extern crate cortex_m_rt as rt; +extern crate cortex_m_semihosting as sh; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::delay::Delay; +use crate::hal::prelude::*; +use crate::rt::entry; +use crate::rt::ExceptionFrame; + +use crate::sh::hio; +use core::fmt::Write; + +#[entry] +fn main() -> ! { + let mut hstdout = hio::hstdout().unwrap(); + + writeln!(hstdout, "Hello, world!").unwrap(); + + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); // .constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + // Try a different clock configuration + let clocks = rcc.cfgr.hclk(8.MHz()).freeze(&mut flash.acr, &mut pwr); + // let clocks = rcc.cfgr + // .sysclk(64.MHz()) + // .pclk1(32.MHz()) + // .freeze(&mut flash.acr); + + // let mut gpioc = dp.GPIOC.split(&mut rcc.ahb2); + // let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.afrh); + + let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + let mut led = gpiob + .pb3 + .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper); + + let mut timer = Delay::new(cp.SYST, clocks); + loop { + // block!(timer.wait()).unwrap(); + timer.delay_ms(1000_u32); + led.set_high(); + // block!(timer.wait()).unwrap(); + timer.delay_ms(1000_u32); + led.set_low(); + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/can-loopback.rs b/stm32l4xx-hal/examples/can-loopback.rs new file mode 100644 index 0000000..40dc939 --- /dev/null +++ b/stm32l4xx-hal/examples/can-loopback.rs @@ -0,0 +1,98 @@ +//! Run the bxCAN peripheral in loopback mode. + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use bxcan::{ + filter::Mask32, + {Frame, StandardId}, +}; +use panic_halt as _; +use rtic::app; +use rtt_target::{rprintln, rtt_init_print}; +use stm32l4xx_hal::{can::Can, prelude::*}; + +#[app(device = stm32l4xx_hal::stm32, peripherals = true)] +const APP: () = { + #[init] + fn init(cx: init::Context) { + rtt_init_print!(); + + let dp = cx.device; + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + + // Set the clocks to 80 MHz + let _clocks = rcc.cfgr.sysclk(80.MHz()).freeze(&mut flash.acr, &mut pwr); + + rprintln!(" - CAN init"); + + let can = { + let rx = + gpioa + .pa11 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + let tx = + gpioa + .pa12 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + + let can = Can::new(&mut rcc.apb1r1, dp.CAN1, (tx, rx)); + + bxcan::Can::builder(can) + } + // APB1 (PCLK1): 80 MHz, Bit rate: 100kBit/s, Sample Point 87.5% + // Value was calculated with http://www.bittiming.can-wiki.info/ + .set_bit_timing(0x001c_0031) + .set_loopback(true); + + // Enable and wait for bxCAN sync to bus + let mut can = can.enable(); + + // Configure filters so that can frames can be received. + let mut filters = can.modify_filters(); + filters.enable_bank(0, Mask32::accept_all()); + + // Drop filters to leave filter configuraiton mode. + drop(filters); + + // Send a frame + let mut test: [u8; 8] = [0; 8]; + let id: u16 = 0x500; + + test[0] = 72; + test[1] = 1; + test[2] = 2; + test[3] = 3; + test[4] = 4; + test[5] = 5; + test[6] = 6; + test[7] = 7; + let test_frame = Frame::new_data(StandardId::new(id).unwrap(), test); + can.transmit(&test_frame).unwrap(); + + // Wait for TX to finish + while !can.is_transmitter_idle() {} + + rprintln!(" - CAN tx complete: {:?}", test_frame); + + // Receive the packet back + let r = can.receive(); + + rprintln!(" - CAN rx {:?}", r); + + assert_eq!(Ok(test_frame), r); + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + continue; + } + } +}; diff --git a/stm32l4xx-hal/examples/i2c_write.rs b/stm32l4xx-hal/examples/i2c_write.rs new file mode 100644 index 0000000..2f14153 --- /dev/null +++ b/stm32l4xx-hal/examples/i2c_write.rs @@ -0,0 +1,87 @@ +//! Blinks an LED + +#![no_std] +#![no_main] + +extern crate cortex_m; +#[macro_use] +extern crate cortex_m_rt as rt; +extern crate cortex_m_semihosting as sh; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; + +use crate::hal::prelude::*; + +use crate::hal::i2c; +use crate::hal::i2c::I2c; +use crate::rt::entry; +use crate::rt::ExceptionFrame; + +use crate::sh::hio; +use core::fmt::Write; + +#[entry] +fn main() -> ! { + let mut hstdout = hio::hstdout().unwrap(); + + // writeln!(hstdout, "Hello, world!").unwrap(); + + // let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + + let mut scl = + gpioa + .pa9 + .into_alternate_open_drain(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + scl.internal_pull_up(&mut gpioa.pupdr, true); + + let mut sda = + gpioa + .pa10 + .into_alternate_open_drain(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + sda.internal_pull_up(&mut gpioa.pupdr, true); + + let mut i2c = I2c::i2c1( + dp.I2C1, + (scl, sda), + i2c::Config::new(100.kHz(), clocks), + &mut rcc.apb1r1, + ); + + // i2c.write(0x3C, &[0xCC, 0xAA]).unwrap(); + let mut buffer = [0u8; 2]; + // 0x08 is version reg + // i2c.write(0x6C, &[0x08],).unwrap(); + // let val = i2c.read(0x36, &mut buffer).unwrap(); + const MAX17048_ADDR: u8 = 0x6C; + i2c.write_read(MAX17048_ADDR, &[0x08], &mut buffer).unwrap(); + let version: u16 = (buffer[0] as u16) << 8 | buffer[1] as u16; + writeln!(hstdout, "Silicon Version: {}", version).ok(); + + // let soc: u16 = (buffer[0] as u16) + (buffer[1] as u16 / 256); //& 0xFF00 + // let soc: u16 = (buffer[0] as u16) << 8 & 0xFF00 | (buffer[1] as u16) & 0x00FF; + i2c.write_read(MAX17048_ADDR, &[0x04], &mut buffer).unwrap(); + let soc: u16 = (buffer[0] as u16) << 8 | buffer[1] as u16; + writeln!(hstdout, "Batt SoC: {}%", soc / 256).ok(); + + i2c.write_read(MAX17048_ADDR, &[0x02], &mut buffer).unwrap(); + let vlt: u16 = (buffer[0] as u16) << 8 | buffer[1] as u16; + writeln!(hstdout, "Volt: {}", vlt as f32 * 0.000078125).ok(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/irq_button.rs b/stm32l4xx-hal/examples/irq_button.rs new file mode 100644 index 0000000..60cc27b --- /dev/null +++ b/stm32l4xx-hal/examples/irq_button.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] + +extern crate cortex_m; +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; + +use crate::hal::{ + gpio::{gpioc::PC13, Edge, ExtiPin, Input, PullUp}, + interrupt, + prelude::*, + stm32, +}; +use core::cell::RefCell; +use core::ops::DerefMut; +use cortex_m::{ + interrupt::{free, Mutex}, + peripheral::NVIC, +}; +use rt::entry; + +// Set up global state. It's all mutexed up for concurrency safety. +static BUTTON: Mutex>>>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + if let Some(mut dp) = stm32::Peripherals::take() { + dp.RCC.apb2enr.write(|w| w.syscfgen().set_bit()); + + let mut rcc = dp.RCC.constrain(); + let mut flash = dp.FLASH.constrain(); // .constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + rcc.cfgr + .hclk(48.MHz()) + .sysclk(80.MHz()) + .pclk1(24.MHz()) + .pclk2(24.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // Create a button input with an interrupt + let mut gpioc = dp.GPIOC.split(&mut rcc.ahb2); + let mut board_btn = gpioc + .pc13 + .into_pull_up_input(&mut gpioc.moder, &mut gpioc.pupdr); + board_btn.make_interrupt_source(&mut dp.SYSCFG, &mut rcc.apb2); + board_btn.enable_interrupt(&mut dp.EXTI); + board_btn.trigger_on_edge(&mut dp.EXTI, Edge::Falling); + + // Enable interrupts + unsafe { + NVIC::unmask(stm32::Interrupt::EXTI15_10); + } + + free(|cs| { + BUTTON.borrow(cs).replace(Some(board_btn)); + }); + + loop { + continue; + } + } + + loop { + continue; + } +} + +#[interrupt] +fn EXTI15_10() { + free(|cs| { + let mut btn_ref = BUTTON.borrow(cs).borrow_mut(); + if let Some(ref mut btn) = btn_ref.deref_mut() { + if btn.check_interrupt() { + // if we don't clear this bit, the ISR would trigger indefinitely + btn.clear_interrupt_pending_bit(); + } + } + }); +} diff --git a/stm32l4xx-hal/examples/lptim_rtic.rs b/stm32l4xx-hal/examples/lptim_rtic.rs new file mode 100644 index 0000000..5c2c0f2 --- /dev/null +++ b/stm32l4xx-hal/examples/lptim_rtic.rs @@ -0,0 +1,94 @@ +//! Test with +//! cargo flash --release --features stm32l4x2,rt --chip STM32L452RETx --example lptim_rtic --target thumbv7em-none-eabi +//! on Nucleo-L452-P board +#![deny(unsafe_code)] +#![no_main] +#![no_std] +extern crate panic_rtt_target; + +use rtt_target::rprintln; +use stm32l4xx_hal::{ + flash::ACR, + gpio::{gpiob::PB13, Output, PinState, PushPull}, + lptimer::{ClockSource, Event, LowPowerTimer, LowPowerTimerConfig, PreScaler}, + pac::LPTIM1, + prelude::*, + pwr::Pwr, + rcc::{ClockSecuritySystem, Clocks, CrystalBypass, RccExt, CFGR}, +}; + +// this is the LD4 on Nucleo-L452-P +type Led = PB13>; +type Timer = LowPowerTimer; + +pub fn configure_clock_tree(cfgr: CFGR, acr: &mut ACR, pwr: &mut Pwr) -> Clocks { + cfgr.lse(CrystalBypass::Disable, ClockSecuritySystem::Disable) + .sysclk(80.MHz()) + .freeze(acr, pwr) +} + +#[rtic::app(device = stm32l4xx_hal::pac, peripherals = true)] +const APP: () = { + struct Resources { + led: Led, + lptim: Timer, + } + + #[init] + fn init(ctx: init::Context) -> init::LateResources { + rtt_target::rtt_init_print!(); + + rprintln!("Init start"); + let device = ctx.device; + // Configure the clock. + let mut rcc = device.RCC.constrain(); + let mut flash = device.FLASH.constrain(); + let mut pwr = device.PWR.constrain(&mut rcc.apb1r1); + let mut gpiob = device.GPIOB.split(&mut rcc.ahb2); + let clocks = configure_clock_tree(rcc.cfgr, &mut flash.acr, &mut pwr); + + // PB13 is a user led on Nucleo-L452-P board + let led = gpiob.pb13.into_push_pull_output_in_state( + &mut gpiob.moder, + &mut gpiob.otyper, + PinState::Low, + ); + rprintln!("Clocks = {:#?}", clocks); + let lptim_config = LowPowerTimerConfig::default() + .clock_source(ClockSource::LSE) + .prescaler(PreScaler::U1) + .arr_value(32_768u16); // roughly 1s + let mut lptim = LowPowerTimer::lptim1( + device.LPTIM1, + lptim_config, + &mut rcc.apb1r1, + &mut rcc.ccipr, + clocks, + ); + lptim.listen(Event::AutoReloadMatch); + init::LateResources { lptim, led } + } + + #[task(binds = LPTIM1, resources = [lptim, led])] + fn timer_tick(ctx: timer_tick::Context) { + let timer_tick::Resources { lptim, led } = ctx.resources; + if lptim.is_event_triggered(Event::AutoReloadMatch) { + lptim.clear_event_flag(Event::AutoReloadMatch); + rprintln!("LPTIM1 tick"); + + led.toggle(); + } + } + + #[idle] + fn idle(_ctx: idle::Context) -> ! { + loop { + // See https://github.com/probe-rs/probe-rs/issues/350 + core::hint::spin_loop(); + } + } + + extern "C" { + fn LCD(); + } +}; diff --git a/stm32l4xx-hal/examples/otg_fs_serial.rs b/stm32l4xx-hal/examples/otg_fs_serial.rs new file mode 100644 index 0000000..5251342 --- /dev/null +++ b/stm32l4xx-hal/examples/otg_fs_serial.rs @@ -0,0 +1,149 @@ +//! OTG USB 2.0 FS serial port example using polling in a busy loop. +//! +//! Note: Must build with features "stm32l4x5 otg_fs" or "stm32l4x6 otg_fs". +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_rt::entry; +use stm32l4xx_hal::gpio::Speed; +use stm32l4xx_hal::otg_fs::{UsbBus, USB}; +use stm32l4xx_hal::prelude::*; +use stm32l4xx_hal::rcc::{ + ClockSecuritySystem, CrystalBypass, MsiFreq, PllConfig, PllDivider, PllSource, +}; +use stm32l4xx_hal::stm32::{Peripherals, CRS, PWR, RCC}; +use usb_device::prelude::*; + +/// Enable CRS (Clock Recovery System) +fn enable_crs() { + let rcc = unsafe { &(*RCC::ptr()) }; + rcc.apb1enr1.modify(|_, w| w.crsen().set_bit()); + let crs = unsafe { &(*CRS::ptr()) }; + // Initialize clock recovery + // Set autotrim enabled. + crs.cr.modify(|_, w| w.autotrimen().set_bit()); + // Enable CR + crs.cr.modify(|_, w| w.cen().set_bit()); +} + +/// Enables VddUSB power supply +fn enable_usb_pwr() { + // Enable PWR peripheral + let rcc = unsafe { &(*RCC::ptr()) }; + rcc.apb1enr1.modify(|_, w| w.pwren().set_bit()); + + // Enable VddUSB + let pwr = unsafe { &*PWR::ptr() }; + pwr.cr2.modify(|_, w| w.usv().set_bit()); +} + +static mut EP_MEMORY: [u32; 1024] = [0; 1024]; + +#[entry] +unsafe fn main() -> ! { + let dp = Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + // Set to true if external 16 MHz high-speed resonator/crystal is used. + const USE_HSE_CLK: bool = true; + + let clocks = { + if !USE_HSE_CLK { + // 48 MHz / 6 * 40 / 4 = 80 MHz + let pll_cfg = PllConfig::new(6, 40, PllDivider::Div4); + + // Note: If program needs low-speed clocks, adjust this. + rcc.cfgr + .msi(MsiFreq::RANGE48M) // Set the MSI (multi-speed internal) clock to 48 MHz + .pll_source(PllSource::MSI) + .sysclk_with_pll(80.MHz(), pll_cfg) + .pclk1(24.MHz()) + .pclk2(24.MHz()) + .freeze(&mut flash.acr, &mut pwr) + } else { + // Note: If program needs low-speed clocks, adjust this. + // Tested using a 16 MHz resonator. + rcc.cfgr + .msi(MsiFreq::RANGE48M) + .hse( + 16.MHz(), + CrystalBypass::Disable, // Bypass enabled when clock signals instead of crystals/resonators are used. + ClockSecuritySystem::Disable, // We have not set up interrupt routines handling clock drifts/errors. + ) + .pll_source(PllSource::HSE) + .sysclk(80.MHz()) + .freeze(&mut flash.acr, &mut pwr) + } + }; + + // Enable clock recovery system. + enable_crs(); + // Enable USB power (and disable VddUSB power isolation). + enable_usb_pwr(); + + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + + let usb = USB { + usb_global: dp.OTG_FS_GLOBAL, + usb_device: dp.OTG_FS_DEVICE, + usb_pwrclk: dp.OTG_FS_PWRCLK, + hclk: clocks.hclk(), + pin_dm: gpioa + .pa11 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh) + .set_speed(Speed::VeryHigh), + pin_dp: gpioa + .pa12 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh) + .set_speed(Speed::VeryHigh), + }; + + let usb_bus = UsbBus::new(usb, &mut EP_MEMORY); + + let mut usb_serial = usbd_serial::SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake Company") + .product("Serial port") + .serial_number("TEST") + .device_class(usbd_serial::USB_CLASS_CDC) + .build(); + + #[cfg(feature = "semihosting")] + hprintln!("Polling!").ok(); + + loop { + if !usb_dev.poll(&mut [&mut usb_serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match usb_serial.read(&mut buf) { + Ok(count) if count > 0 => { + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match usb_serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + } + _ => {} + } + } + } + _ => {} + } + } +} diff --git a/stm32l4xx-hal/examples/pll_config.rs b/stm32l4xx-hal/examples/pll_config.rs new file mode 100644 index 0000000..130eb99 --- /dev/null +++ b/stm32l4xx-hal/examples/pll_config.rs @@ -0,0 +1,88 @@ +//! Test the serial interface +//! +//! This example requires you to short (connect) the TX and RX pins. +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::prelude::*; +use crate::hal::serial::{Config, Serial}; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + let clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // The Serial API is highly generic + // TRY the commented out, different pin configurations + let tx = gpioa + .pa9 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + // let tx = gpiob.pb6.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + let rx = gpioa + .pa10 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + // let rx = gpiob.pb7.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart1( + p.USART1, + (tx, rx), + Config::default().baudrate(9_600.bps()), + clocks, + &mut rcc.apb2, + ); + let (mut tx, mut rx) = serial.split(); + + let sent = b'X'; + + // The `block!` macro makes an operation block until it finishes + // NOTE the error type is `!` + + block!(tx.write(sent)).ok(); + + let received = block!(rx.read()).unwrap(); + + assert_eq!(received, sent); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/pwm.rs b/stm32l4xx-hal/examples/pwm.rs new file mode 100644 index 0000000..e64556c --- /dev/null +++ b/stm32l4xx-hal/examples/pwm.rs @@ -0,0 +1,76 @@ +//! Testing PWM output + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +// use cortex_m::asm; +use cortex_m_rt::entry; +use stm32l4xx_hal::{delay, prelude::*, stm32}; + +#[entry] +fn main() -> ! { + let c = cortex_m::Peripherals::take().unwrap(); + let p = stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + + // TIM2 + let c1 = gpioa + .pa0 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let c2 = gpioa + .pa1 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let c3 = gpioa + .pa2 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let c4 = gpioa + .pa3 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + + let mut pwm = p + .TIM2 + .pwm((c1, c2, c3, c4), 1.kHz(), clocks, &mut rcc.apb1r1) + .3; + + let max = pwm.get_max_duty(); + + pwm.enable(); + + let mut timer = delay::Delay::new(c.SYST, clocks); + let second: u32 = 100; + + // NB: if the pins are LEDs, brightness is not + // linear in duty value. + loop { + pwm.set_duty(max); + timer.delay_ms(second); + // asm::bkpt(); + + pwm.set_duty(max / 11 * 10); + timer.delay_ms(second); + // asm::bkpt(); + + pwm.set_duty(3 * max / 4); + timer.delay_ms(second); + // asm::bkpt(); + + pwm.set_duty(max / 2); + timer.delay_ms(second); + // asm::bkpt(); + + pwm.set_duty(max / 4); + timer.delay_ms(second); + // asm::bkpt(); + } +} diff --git a/stm32l4xx-hal/examples/qspi.rs b/stm32l4xx-hal/examples/qspi.rs new file mode 100644 index 0000000..e383b54 --- /dev/null +++ b/stm32l4xx-hal/examples/qspi.rs @@ -0,0 +1,91 @@ +//! Test the Quad SPI interface +//! +//! The example wirtes a command over the QSPI interfaces and recives a 3 byte response. +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +// #[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::prelude::*; +use crate::hal::qspi::{Qspi, QspiConfig, QspiMode, QspiReadCommand}; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut gpioe = p.GPIOE.split(&mut rcc.ahb2); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + // clock configuration (clocks run at nearly the maximum frequency) + let _clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + let get_id_command = QspiReadCommand { + instruction: Some((0x9f, QspiMode::SingleChannel)), + address: None, + alternative_bytes: None, + dummy_cycles: 0, + data_mode: QspiMode::SingleChannel, + receive_length: 3, + double_data_rate: false, + }; + let mut id_arr: [u8; 3] = [0; 3]; + + let qspi = { + let clk = gpioe + .pe10 + .into_alternate(&mut gpioe.moder, &mut gpioe.otyper, &mut gpioe.afrh); + let ncs = gpioe + .pe11 + .into_alternate(&mut gpioe.moder, &mut gpioe.otyper, &mut gpioe.afrh); + let io_0 = gpioe + .pe12 + .into_alternate(&mut gpioe.moder, &mut gpioe.otyper, &mut gpioe.afrh); + let io_1 = gpioe + .pe13 + .into_alternate(&mut gpioe.moder, &mut gpioe.otyper, &mut gpioe.afrh); + let io_2 = gpioe + .pe14 + .into_alternate(&mut gpioe.moder, &mut gpioe.otyper, &mut gpioe.afrh); + let io_3 = gpioe + .pe15 + .into_alternate(&mut gpioe.moder, &mut gpioe.otyper, &mut gpioe.afrh); + Qspi::new( + p.QUADSPI, + (clk, ncs, io_0, io_1, io_2, io_3), + &mut rcc.ahb3, + QspiConfig::default().clock_prescaler(201), + ) //Added due to missing OSPEEDR register changes in Qspi + }; + + qspi.transfer(get_id_command, &mut id_arr).unwrap(); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/rng.rs b/stm32l4xx-hal/examples/rng.rs new file mode 100644 index 0000000..1c3b947 --- /dev/null +++ b/stm32l4xx-hal/examples/rng.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +extern crate panic_halt; +extern crate stm32l4xx_hal as hal; + +use core::fmt; +use cortex_m_rt::entry; + +use crate::hal::delay::Delay; +use crate::hal::prelude::*; +use crate::hal::serial::{Config, Serial}; +use crate::hal::stm32; +use hal::hal::blocking::rng::Read; + +macro_rules! uprint { + ($serial:expr, $($arg:tt)*) => { + fmt::write($serial, format_args!($($arg)*)).ok() + }; +} + +macro_rules! uprintln { + ($serial:expr, $fmt:expr) => { + uprint!($serial, concat!($fmt, "\n")) + }; + ($serial:expr, $fmt:expr, $($arg:tt)*) => { + uprint!($serial, concat!($fmt, "\n"), $($arg)*) + }; +} + +#[entry] +fn main() -> ! { + let core = cortex_m::Peripherals::take().unwrap(); + let device = stm32::Peripherals::take().unwrap(); + + let mut flash = device.FLASH.constrain(); + let mut rcc = device.RCC.constrain(); + let mut pwr = device.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc + .cfgr + .hsi48(true) // needed for RNG + .sysclk(64.MHz()) + .pclk1(32.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // setup usart + let mut gpioa = device.GPIOA.split(&mut rcc.ahb2); + let tx = gpioa + .pa9 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + let rx = gpioa + .pa10 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + + let baud_rate = 9_600; // 115_200; + let serial = Serial::usart1( + device.USART1, + (tx, rx), + Config::default().baudrate(baud_rate.bps()), + clocks, + &mut rcc.apb2, + ); + let (mut tx, _) = serial.split(); + + // get a timer + let mut timer = Delay::new(core.SYST, clocks); + + // setup rng + let mut rng = device.RNG.enable(&mut rcc.ahb2, clocks); + + uprintln!(&mut tx, "{:?}", clocks); + + let some_time: u32 = 500; + loop { + const N: usize = 5; + let mut random_bytes = [0u8; N]; + rng.read(&mut random_bytes) + .expect("missing random data for some reason"); + uprintln!(&mut tx, "{} random u8 values: {:?}", N, random_bytes); + + timer.delay_ms(some_time); + } +} diff --git a/stm32l4xx-hal/examples/rtc.rs b/stm32l4xx-hal/examples/rtc.rs new file mode 100644 index 0000000..d275ab7 --- /dev/null +++ b/stm32l4xx-hal/examples/rtc.rs @@ -0,0 +1,76 @@ +//! Blinks an LED + +#![no_std] +#![no_main] + +extern crate cortex_m; +#[macro_use] +extern crate cortex_m_rt as rt; +extern crate cortex_m_semihosting as sh; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::datetime::{Date, Time}; +use crate::hal::delay::Delay; +use crate::hal::prelude::*; +use crate::hal::rcc::{ClockSecuritySystem, CrystalBypass}; +use crate::hal::rtc::{Rtc, RtcClockSource, RtcConfig}; +use crate::rt::ExceptionFrame; + +use crate::sh::hio; +use core::fmt::Write; + +#[entry] +fn main() -> ! { + let mut hstdout = hio::hstdout().unwrap(); + + writeln!(hstdout, "Hello, world!").unwrap(); + + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + // Try a different clock configuration + let clocks = rcc + .cfgr + .lse(CrystalBypass::Disable, ClockSecuritySystem::Disable) + .freeze(&mut flash.acr, &mut pwr); + + let mut timer = Delay::new(cp.SYST, clocks); + + let mut rtc = Rtc::rtc( + dp.RTC, + &mut rcc.apb1r1, + &mut rcc.bdcr, + &mut pwr.cr1, + RtcConfig::default().clock_config(RtcClockSource::LSE), + ); + + let time = Time::new(21.hours(), 57.minutes(), 32.secs(), 0.micros(), false); + let date = Date::new(1.day(), 24.date(), 4.month(), 2018.year()); + + rtc.set_date_time(date, time); + + timer.delay_ms(1000_u32); + timer.delay_ms(1000_u32); + timer.delay_ms(1000_u32); + + let (rtc_date, rtc_time) = rtc.get_date_time(); + + writeln!(hstdout, "Time: {:?}", rtc_time).unwrap(); + writeln!(hstdout, "Date: {:?}", rtc_date).unwrap(); + writeln!(hstdout, "Good bye!").unwrap(); + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/rtc_alarm.rs b/stm32l4xx-hal/examples/rtc_alarm.rs new file mode 100644 index 0000000..03096d4 --- /dev/null +++ b/stm32l4xx-hal/examples/rtc_alarm.rs @@ -0,0 +1,96 @@ +//! Sets an RTC alarm + +#![no_std] +#![no_main] + +extern crate cortex_m; +#[macro_use] +extern crate cortex_m_rt as rt; +extern crate cortex_m_semihosting as sh; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; + +use crate::hal::datetime::{Date, Time}; +use crate::hal::prelude::*; +use crate::hal::rcc::{ClockSecuritySystem, CrystalBypass}; +use crate::hal::rtc::{Event, Rtc, RtcClockSource, RtcConfig}; +use crate::rt::ExceptionFrame; +use cortex_m::interrupt::{free, Mutex}; + +use crate::sh::hio; +use core::{cell::RefCell, fmt::Write, ops::DerefMut}; +use hal::interrupt; +use hal::pac; +use pac::NVIC; + +static RTC: Mutex>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + let mut hstdout = hio::hstdout().unwrap(); + + writeln!(hstdout, "Hello, world!").unwrap(); + + let mut dp = hal::stm32::Peripherals::take().unwrap(); + dp.RCC.apb2enr.write(|w| w.syscfgen().set_bit()); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + // Try a different clock configuration + rcc.cfgr + .lse(CrystalBypass::Disable, ClockSecuritySystem::Disable) + .freeze(&mut flash.acr, &mut pwr); + + let mut rtc = Rtc::rtc( + dp.RTC, + &mut rcc.apb1r1, + &mut rcc.bdcr, + &mut pwr.cr1, + RtcConfig::default().clock_config(RtcClockSource::LSE), + ); + + let time = Time::new(21.hours(), 57.minutes(), 32.secs(), 0.micros(), false); + let date = Date::new(1.day(), 24.date(), 4.month(), 2018.year()); + + rtc.set_date_time(date, time); + + // Set alarm A for 1 minute + // let alarm_time = Time::new(21.hours(), 57.minutes(), 37.secs(), 0.micros(), false); + // let alarm_date = date; + // rtc.set_alarm(Alarm::AlarmA, alarm_date, alarm_time); + let mut wkp = rtc.wakeup_timer(); + wkp.start(15_u32); + rtc.listen(&mut dp.EXTI, Event::WakeupTimer); + + unsafe { + NVIC::unmask(pac::Interrupt::RTC_WKUP); + } + + free(|cs| { + RTC.borrow(cs).replace(Some(rtc)); + }); + + loop { + continue; + } +} + +#[interrupt] +fn RTC_WKUP() { + let mut hstdout = hio::hstdout().unwrap(); + free(|cs| { + let mut rtc_ref = RTC.borrow(cs).borrow_mut(); + if let Some(ref mut rtc) = rtc_ref.deref_mut() { + if rtc.check_interrupt(Event::WakeupTimer, true) { + writeln!(hstdout, "RTC Wakeup!").unwrap(); + } + } + }); +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/rtic_frame_serial_dma.rs b/stm32l4xx-hal/examples/rtic_frame_serial_dma.rs new file mode 100644 index 0000000..0474265 --- /dev/null +++ b/stm32l4xx-hal/examples/rtic_frame_serial_dma.rs @@ -0,0 +1,156 @@ +//! Test the serial interface with the frame DMA engine in RTIC. This will echo frames sent to the +//! board via the debuggers VCP. +//! +//! This is tested on Nucleo-64 STM32L412 over the debuggers VCP. +//! +//! This example only compiles for some targets so it is not part of the CI for now. + +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +use hal::{ + dma::{self, DMAFrame, FrameReader, FrameSender}, + pac::USART2, + prelude::*, + rcc::{ClockSecuritySystem, CrystalBypass, MsiFreq}, + serial::{self, Config, Serial}, +}; +use heapless::{ + pool, + pool::singleton::{Box, Pool}, +}; +use panic_halt as _; +use rtic::app; +use stm32l4xx_hal as hal; +use stm32l4xx_hal::dma::{RxDma, TxDma}; +use stm32l4xx_hal::serial::{Rx, Tx}; + +// The pool gives out `Box`s that can hold 8 bytes +pool!( + #[allow(non_upper_case_globals)] + SerialDMAPool: DMAFrame<8> +); + +#[app(device = stm32l4xx_hal::stm32, peripherals = true)] +const APP: () = { + struct Resources { + frame_reader: FrameReader, RxDma, dma::dma1::C6>, 8>, + frame_sender: FrameSender, TxDma, dma::dma1::C7>, 8>, + } + + #[init] + fn init(cx: init::Context) -> init::LateResources { + static mut MEMORY: [u8; 1024] = [0; 1024]; + + // increase the capacity of the pool by ~8 blocks + SerialDMAPool::grow(MEMORY); + + let dp = cx.device; + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + + // Set the clocks to 80 MHz + let clocks = rcc + .cfgr + .lse(CrystalBypass::Disable, ClockSecuritySystem::Disable) + .msi(MsiFreq::RANGE4M) + .sysclk(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // USART2 pins + let tx2 = gpioa + .pa2 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let rx2 = gpioa + .pa3 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + + // We will listen for the character `a`, this can be changed to any character such as `\0` + // if using COBS encoding, or `\n` if using string encoding. + let mut serial = Serial::usart2( + dp.USART2, + (tx2, rx2), + Config::default() + .baudrate(115_200.bps()) + .character_match(b'a'), + clocks, + &mut rcc.apb1r1, + ); + serial.listen(serial::Event::CharacterMatch); + let (serial_tx, serial_rx) = serial.split(); + + let channels = dp.DMA1.split(&mut rcc.ahb1); + let mut dma_ch6 = channels.6; + let mut dma_ch7 = channels.7; + dma_ch6.listen(dma::Event::TransferComplete); + dma_ch7.listen(dma::Event::TransferComplete); + + // Serial frame reader (DMA based), give it a buffer to start reading into + let fr = if let Some(dma_buf) = SerialDMAPool::alloc() { + // Set up the first reader frame + let dma_buf = dma_buf.init(DMAFrame::new()); + serial_rx.with_dma(dma_ch6).frame_reader(dma_buf) + } else { + unreachable!() + }; + + // Serial frame sender (DMA based) + let fs: FrameSender, _, 8> = serial_tx.with_dma(dma_ch7).frame_sender(); + + init::LateResources { + frame_reader: fr, + frame_sender: fs, + } + } + + /// This task handles the character match interrupt at required by the `FrameReader` + /// + /// It will echo the buffer back to the serial. + #[task(binds = USART2, resources = [frame_reader, frame_sender], priority = 3)] + fn serial_isr(cx: serial_isr::Context) { + // Check for character match + if cx.resources.frame_reader.check_character_match(true) { + if let Some(dma_buf) = SerialDMAPool::alloc() { + let dma_buf = dma_buf.init(DMAFrame::new()); + let buf = cx.resources.frame_reader.character_match_interrupt(dma_buf); + + // Echo the buffer back over the serial + cx.resources.frame_sender.send(buf).ok(); + } + } + } + + /// This task handles the RX transfer complete interrupt at required by the `FrameReader` + /// + /// In this case we are discarding if a frame gets full as no character match was received + #[task(binds = DMA1_CH6, resources = [frame_reader], priority = 3)] + fn serial_rx_dma_isr(cx: serial_rx_dma_isr::Context) { + if let Some(dma_buf) = SerialDMAPool::alloc() { + let dma_buf = dma_buf.init(DMAFrame::new()); + + // Erroneous packet as it did not fit in a buffer, throw away the buffer + let _buf = cx + .resources + .frame_reader + .transfer_complete_interrupt(dma_buf); + } + } + + /// This task handles the TX transfer complete interrupt at required by the `FrameSender` + #[task(binds = DMA1_CH7, resources = [frame_sender], priority = 3)] + fn serial_tx_dma_isr(cx: serial_tx_dma_isr::Context) { + let fs = cx.resources.frame_sender; + + if let Some(_buf) = fs.transfer_complete_interrupt() { + // Frame sent, drop the buffer to return it too the pool + } + + // Send a new buffer + // fs.send(buffer); + } +}; diff --git a/stm32l4xx-hal/examples/serial.rs b/stm32l4xx-hal/examples/serial.rs new file mode 100644 index 0000000..fe7949d --- /dev/null +++ b/stm32l4xx-hal/examples/serial.rs @@ -0,0 +1,85 @@ +//! Test the serial interface +//! +//! This example requires you to short (connect) the TX and RX pins. +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::prelude::*; +use crate::hal::serial::Serial; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + let clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // The Serial API is highly generic + // TRY the commented out, different pin configurations + // let tx = gpioa.pa9.into_af7_pushpull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + let tx = gpioa + .pa2 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let tx = gpiob.pb6.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // let rx = gpioa.pa10.into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + let rx = gpioa + .pa3 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let rx = gpiob.pb7.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart2(p.USART2, (tx, rx), 9_600.bps(), clocks, &mut rcc.apb1r1); + let (mut tx, mut rx) = serial.split(); + + let sent = b'X'; + + // The `block!` macro makes an operation block until it finishes + // NOTE the error type is `!` + + block!(tx.write(sent)).ok(); + + let received = block!(rx.read()).unwrap(); + + assert_eq!(received, sent); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/serial_dma.rs b/stm32l4xx-hal/examples/serial_dma.rs new file mode 100644 index 0000000..ec43b6c --- /dev/null +++ b/stm32l4xx-hal/examples/serial_dma.rs @@ -0,0 +1,149 @@ +//! Test the serial interface with the DMA engine +//! +//! This example requires you to short (connect) the TX and RX pins. +#![no_main] +#![no_std] + +#[macro_use(singleton)] +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::dma::CircReadDma; +use crate::hal::prelude::*; +use crate::hal::serial::{Config, Serial}; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + let channels = p.DMA1.split(&mut rcc.ahb1); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + // let clocks = rcc.cfgr.sysclk(64.MHz()).pclk1(32.MHz()).freeze(&mut flash.acr); + + // The Serial API is highly generic + // TRY the commented out, different pin configurations + let tx = gpioa + .pa9 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + // let tx = gpiob.pb6.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + let rx = gpioa + .pa10 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + // let rx = gpiob.pb7.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart1( + p.USART1, + (tx, rx), + Config::default().baudrate(9_600.bps()), + clocks, + &mut rcc.apb2, + ); + let (mut tx, rx) = serial.split(); + + let buf = singleton!(: [u8; 9] = [0; 9]).unwrap(); + let mut circ_buffer = rx.with_dma(channels.5).circ_read(buf); + let mut rx_buf = [0; 9]; + + // single byte send/receive + send(&mut tx, b"x"); + let rx_len = circ_buffer.read(&mut rx_buf).unwrap(); + assert_eq!(rx_len, 1); + assert_eq!(&rx_buf[..1], b"x"); + + // multi byte send/receive + send(&mut tx, b"12345678"); + let rx_len = circ_buffer.read(&mut rx_buf).unwrap(); + assert_eq!(rx_len, 8); + assert_eq!(&rx_buf[..8], b"12345678"); + + // Checking three types of overflow detection + // 1. write pointer passes read pointer + send(&mut tx, b"12345678"); // write-pointer -> 8 + let rx_len = circ_buffer.read(&mut rx_buf[..1]).unwrap(); // read-pointer -> 1 + assert_eq!(rx_len, 1); + send(&mut tx, b"12"); // write-pointer -> 1 (catches up with read-pointer) + let rx_res = circ_buffer.read(&mut rx_buf[..1]); + if let Err(hal::dma::Error::Overrun) = rx_res { + } else { + panic!("An overrun should have been detected"); + } + + // 2. transfer complete flag set but it looks like the write-pointer did not pass 0 + send(&mut tx, b"123456789"); // write-pointer stays 1, transfer complete flag set + send(&mut tx, b"1234"); // write-pointer -> 5 + let rx_res = circ_buffer.read(&mut rx_buf[..]); + if let Err(hal::dma::Error::Overrun) = rx_res { + } else { + panic!("An overrun should have been detected"); + } + + // 3a. half complete flag set but it looks like the write-ptr did not pass ceil(capacity/2) = 5 + send(&mut tx, b"123456789"); // write-pointer stays 5, all flags set + send(&mut tx, b"12345678"); // write-pointer -> 4 + let rx_res = circ_buffer.read(&mut rx_buf[..]); + if let Err(hal::dma::Error::Overrun) = rx_res { + } else { + panic!("An overrun should have been detected"); + } + + // 3b. check that the half complete flag is not yet set at write-pointer = floor(capacity/2) = 4 + send(&mut tx, b"1234"); // write-pointer -> 0 + circ_buffer.read(&mut rx_buf[..]).unwrap(); // read something to prevent overrun + send(&mut tx, b"12345"); // write-pointer -> 4 + circ_buffer + .read(&mut rx_buf[..]) + .expect("No overrun should be detected here"); + + // Undetectable overrun + send(&mut tx, b"123456789"); + send(&mut tx, b"abcdefgh"); // overrun but it looks like only 8 bytes have been written + let rx_len = circ_buffer.read(&mut rx_buf[..]).unwrap(); + assert_eq!(rx_len, 8); + assert_eq!(&rx_buf[..8], b"abcdefgh"); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +fn send(tx: &mut impl embedded_hal::serial::Write, data: &[u8]) { + for byte in data { + if let Err(_) = block!(tx.write(*byte)) { + panic!("serial tx failed"); + } + } + + // waste some time so that the data will be received completely + for _ in 0..10000 { + cortex_m::asm::nop(); + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/serial_dma_us2.rs b/stm32l4xx-hal/examples/serial_dma_us2.rs new file mode 100644 index 0000000..60d5c78 --- /dev/null +++ b/stm32l4xx-hal/examples/serial_dma_us2.rs @@ -0,0 +1,88 @@ +//! Test the serial interface with the DMA engine +//! +//! This example requires you to short (connect) the TX and RX pins. +#![no_main] +#![no_std] + +#[macro_use(singleton)] +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::dma::CircReadDma; +use crate::hal::prelude::*; +use crate::hal::serial::Serial; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + let channels = p.DMA1.split(&mut rcc.ahb1); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + // let clocks = rcc.cfgr.sysclk(64.MHz()).pclk1(32.MHz()).freeze(&mut flash.acr); + + let tx = gpioa + .pa2 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let tx = gpiob.pb6.into_af7_pushpull(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // let rx = gpioa.pa10.into_af7_pushpull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh); + let rx = gpioa + .pa3 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let rx = gpiob.pb7.into_af7_pushpull(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart2(p.USART2, (tx, rx), 115_200.bps(), clocks, &mut rcc.apb1r1); + let (mut tx, rx) = serial.split(); + + let sent = b'X'; + + // The `block!` macro makes an operation block until it finishes + // NOTE the error type is `!` + + block!(tx.write(sent)).ok(); + + let buf = singleton!(: [u8; 8] = [0; 8]).unwrap(); + + let mut circ_buffer = rx.with_dma(channels.6).circ_read(buf); + + let mut rx_buf = [0; 8]; + let rx_len = circ_buffer.read(&mut rx_buf).unwrap(); + + let _received = &rx_buf[..rx_len]; + + // let received = block!(rx.read()).unwrap(); + + // assert_eq!(received, sent); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/serial_echo_rtic.rs b/stm32l4xx-hal/examples/serial_echo_rtic.rs new file mode 100644 index 0000000..e2a8102 --- /dev/null +++ b/stm32l4xx-hal/examples/serial_echo_rtic.rs @@ -0,0 +1,105 @@ +#![no_main] +#![no_std] + +extern crate panic_rtt_target; + +use heapless::{consts::U8, spsc}; +use nb::block; +use rtt_target::{rprint, rprintln}; +use stm32l4xx_hal::{ + pac::{self, USART2}, + prelude::*, + serial::{self, Config, Serial}, +}; + +#[rtic::app(device = stm32l4xx_hal::pac)] +const APP: () = { + struct Resources { + rx: serial::Rx, + tx: serial::Tx, + + rx_prod: spsc::Producer<'static, u8, U8>, + rx_cons: spsc::Consumer<'static, u8, U8>, + } + + #[init] + fn init(_: init::Context) -> init::LateResources { + static mut RX_QUEUE: spsc::Queue = spsc::Queue(heapless::i::Queue::new()); + + rtt_target::rtt_init_print!(); + rprint!("Initializing... "); + + let p = pac::Peripherals::take().unwrap(); + + let mut rcc = p.RCC.constrain(); + let mut flash = p.FLASH.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + + let tx_pin = gpioa + .pa2 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let rx_pin = gpioa + .pa3 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + + let mut serial = Serial::usart2( + p.USART2, + (tx_pin, rx_pin), + Config::default().baudrate(115_200.bps()), + clocks, + &mut rcc.apb1r1, + ); + serial.listen(serial::Event::Rxne); + + let (tx, rx) = serial.split(); + let (rx_prod, rx_cons) = RX_QUEUE.split(); + + rprintln!("done."); + + init::LateResources { + rx, + tx, + + rx_prod, + rx_cons, + } + } + + #[idle(resources = [rx_cons, tx])] + fn idle(cx: idle::Context) -> ! { + let rx = cx.resources.rx_cons; + let tx = cx.resources.tx; + + loop { + if let Some(b) = rx.dequeue() { + rprintln!("Echoing '{}'", b as char); + block!(tx.write(b)).unwrap(); + } + } + } + + #[task(binds = USART2, resources = [rx, rx_prod])] + fn usart2(cx: usart2::Context) { + let rx = cx.resources.rx; + let queue = cx.resources.rx_prod; + + let b = match rx.read() { + Ok(b) => b, + Err(err) => { + rprintln!("Error reading from USART: {:?}", err); + return; + } + }; + match queue.enqueue(b) { + Ok(()) => (), + Err(err) => { + rprintln!("Error adding received byte to queue: {:?}", err); + return; + } + } + } +}; diff --git a/stm32l4xx-hal/examples/serial_half_duplex.rs b/stm32l4xx-hal/examples/serial_half_duplex.rs new file mode 100644 index 0000000..dfe959b --- /dev/null +++ b/stm32l4xx-hal/examples/serial_half_duplex.rs @@ -0,0 +1,88 @@ +//! Test the serial interface in Half-Duplex mode. +//! +//! This example requires you to hook-up a pullup resistor on the TX pin. RX pin is not used. +//! Resistor value depends on the baurate and line caracteristics, 1KOhms works well in most cases. +//! Half-Duplex mode internally connect TX to RX, meaning that bytes sent will also be received. +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::prelude::*; +use crate::hal::serial::{Config, Serial}; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + let clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // The Serial API is highly generic + // TRY the commented out, different pin configurations + // let tx = gpioa.pa9.into_af7(&mut gpioa.moder, &mut gpioa.afrh).set_open_drain(); + let tx = + gpioa + .pa2 + .into_alternate_open_drain(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let tx = gpiob.pb6.into_alternate_open_drain(&mut gpiob.moder, &mut gpioa.otyper, &mut gpiob.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart2( + p.USART2, + (tx,), + Config::default().baudrate(9_600.bps()), + clocks, + &mut rcc.apb1r1, + ); + let (mut tx, mut rx) = serial.split(); + + let sent = b'X'; + + // The `block!` macro makes an operation block until it finishes + // NOTE the error type is `!` + + block!(tx.write(sent)).ok(); + + let received = block!(rx.read()).unwrap(); + + assert_eq!(received, sent); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/serial_hw_flow.rs b/stm32l4xx-hal/examples/serial_hw_flow.rs new file mode 100644 index 0000000..5a301ff --- /dev/null +++ b/stm32l4xx-hal/examples/serial_hw_flow.rs @@ -0,0 +1,98 @@ +//! Test the serial interface +//! +//! This example requires you to short (connect) the TX and RX pins. +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::prelude::*; +use crate::hal::serial::{Config, Serial}; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + let clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // The Serial API is highly generic + // TRY the commented out, different pin configurations + // let tx = gpioa.pa9.into_af7(&mut gpioa.moder, &mut gpioa.afrh); + let tx = gpioa + .pa2 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let tx = gpiob.pb6.into_af7(&mut gpiob.moder, &mut gpiob.afrl); + + // let rx = gpioa.pa10.into_af7(&mut gpioa.moder, &mut gpioa.afrh); + let rx = gpioa + .pa3 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + // let rx = gpiob.pb7.into_af7(&mut gpiob.moder, &mut gpiob.afrl); + + let rts = gpioa + .pa1 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let cts = gpioa + .pa0 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart2( + p.USART2, + (tx, rx, rts, cts), + Config::default().baudrate(9_600.bps()), + clocks, + &mut rcc.apb1r1, + ); + let (mut tx, mut rx) = serial.split(); + + let sent = b'X'; + + // The `block!` macro makes an operation block until it finishes + // NOTE the error type is `!` + + block!(tx.write(sent)).ok(); + + let received = block!(rx.read()).unwrap(); + + assert_eq!(received, sent); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/serial_vcom.rs b/stm32l4xx-hal/examples/serial_vcom.rs new file mode 100644 index 0000000..583b019 --- /dev/null +++ b/stm32l4xx-hal/examples/serial_vcom.rs @@ -0,0 +1,88 @@ +//! Test the serial interface +//! +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +#[macro_use(block)] +extern crate nb; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; +// #[macro_use(block)] +// extern crate nb; + +use crate::hal::prelude::*; +use crate::hal::serial::{Config, Serial}; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + // let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + // let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + let mut gpiod = p.GPIOD.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + // let clocks = rcc.cfgr.sysclk(64.MHz()).pclk1(32.MHz()).freeze(&mut flash.acr); + + //let tx = gpioa.pa2.into_af7(&mut gpioa.moder, &mut gpioa.afrl); + // let tx = gpiob.pb6.into_af7(&mut gpiob.moder, &mut gpiob.afrl); + let tx = gpiod + .pd5 + .into_alternate(&mut gpiod.moder, &mut gpiod.otyper, &mut gpiod.afrl); + + // let rx = gpioa.pa3.into_af7(&mut gpioa.moder, &mut gpioa.afrl); + // let rx = gpiob.pb7.into_af7(&mut gpiob.moder, &mut gpiob.afrl); + let rx = gpiod + .pd6 + .into_alternate(&mut gpiod.moder, &mut gpiod.otyper, &mut gpiod.afrl); + + // TRY using a different USART peripheral here + let serial = Serial::usart2( + p.USART2, + (tx, rx), + Config::default().baudrate(115_200.bps()), + clocks, + &mut rcc.apb1r1, + ); + let (mut tx, mut rx) = serial.split(); + + let sent = b'X'; + + // The `block!` macro makes an operation block until it finishes + // NOTE the error type is `!` + + block!(tx.write(sent)).ok(); + block!(tx.write(sent)).ok(); + block!(tx.write(sent)).ok(); + block!(tx.write(sent)).ok(); + block!(tx.write(sent)).ok(); + + // when using virtual com port for recieve can causes a framing error + // On the stm32l476 discovery it is working fine at 115200 baud + let received = block!(rx.read()).unwrap(); + + assert_eq!(received, sent); + + // if all goes well you should reach this breakpoint + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/spi_dma_rxtx.rs b/stm32l4xx-hal/examples/spi_dma_rxtx.rs new file mode 100644 index 0000000..af69a3a --- /dev/null +++ b/stm32l4xx-hal/examples/spi_dma_rxtx.rs @@ -0,0 +1,101 @@ +//! Test the SPI in RX/TX (transfer) DMA mode +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use panic_rtt_target as _; +use rtt_target::rprintln; +use stm32l4xx_hal::{ + dma::TransferDma, + gpio::{PinState, Speed}, + hal::spi::{Mode, Phase, Polarity}, + prelude::*, + rcc::MsiFreq, + spi::Spi, +}; + +#[rtic::app(device = stm32l4xx_hal::pac, peripherals = true)] +const APP: () = { + #[init] + fn init(cx: init::Context) { + static mut DMA_BUF: [u8; 5] = [0xf0, 0xaa, 0x00, 0xff, 0x0f]; + + rtt_target::rtt_init_print!(); + rprintln!("Initializing... "); + + let dp = cx.device; + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + let dma1_channels = dp.DMA1.split(&mut rcc.ahb1); + + // + // Initialize the clocks to 80 MHz + // + rprintln!(" - Clock init"); + let clocks = rcc + .cfgr + .msi(MsiFreq::RANGE4M) + .sysclk(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + // + // Initialize the SPI + // + let sck = gpiob + .pb3 + .into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl) + .set_speed(Speed::High); + let miso = gpiob + .pb4 + .into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl) + .set_speed(Speed::High); + let mosi = gpiob + .pb5 + .into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl) + .set_speed(Speed::High); + let mut dummy_cs = gpiob.pb6.into_push_pull_output_in_state( + &mut gpiob.moder, + &mut gpiob.otyper, + PinState::High, + ); + let spi = Spi::spi1( + dp.SPI1, + (sck, miso, mosi), + Mode { + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, + }, + 100.kHz(), + clocks, + &mut rcc.apb2, + ); + + // Create DMA SPI + let dma_spi = spi.with_rxtx_dma(dma1_channels.2, dma1_channels.3); + + // Check the buffer before using it + rprintln!("buf pre: 0x{:x?}", &DMA_BUF); + + // Perform transfer and wait for it to finish (blocking), this can also be done using + // interrupts on the desired DMA channel + dummy_cs.set_low(); + let transfer = dma_spi.transfer(DMA_BUF); + let (buf, _dma_spi) = transfer.wait(); + dummy_cs.set_high(); + + // Inspect the extracted buffer, if the MISO is connected to VCC or GND it will be all 0 or + // 1. + rprintln!("buf post: 0x{:x?}", &buf); + } + + // Idle function so RTT keeps working + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + continue; + } + } +}; diff --git a/stm32l4xx-hal/examples/spi_slave.rs b/stm32l4xx-hal/examples/spi_slave.rs new file mode 100644 index 0000000..56e117f --- /dev/null +++ b/stm32l4xx-hal/examples/spi_slave.rs @@ -0,0 +1,75 @@ +//! Interfacing the on-board L3GD20 (gyroscope) +#![no_main] +#![no_std] + +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +extern crate cortex_m; +extern crate embedded_hal as ehal; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; + +use crate::ehal::spi::{Mode, Phase, Polarity}; +use crate::hal::prelude::*; +use crate::hal::spi::Spi; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +/// SPI mode +pub const MODE: Mode = Mode { + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, +}; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + // TRY the other clock configuration + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + let _clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + + // The `L3gd20` abstraction exposed by the `f3` crate requires a specific pin configuration to + // be used and won't accept any configuration other than the one used here. Trying to use a + // different pin configuration will result in a compiler error. + let sck = gpioa + .pa5 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let miso = gpioa + .pa6 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let mosi = gpioa + .pa7 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + + // clock speed is determined by the master + let mut spi = Spi::spi1_slave(p.SPI1, (sck, miso, mosi), MODE, &mut rcc.apb2); + + let mut data = [0x1]; + // this will block until the master starts the clock + spi.transfer(&mut data).unwrap(); + + // when you reach this breakpoint you'll be able to inspect the variable `data` which contains the + // data sent by the master + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/spi_write.rs b/stm32l4xx-hal/examples/spi_write.rs new file mode 100644 index 0000000..2598d01 --- /dev/null +++ b/stm32l4xx-hal/examples/spi_write.rs @@ -0,0 +1,97 @@ +//! Interfacing the on-board L3GD20 (gyroscope) +#![no_main] +#![no_std] + +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +extern crate cortex_m; +extern crate embedded_hal as ehal; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; + +use crate::ehal::spi::{Mode, Phase, Polarity}; +use crate::hal::prelude::*; +use crate::hal::spi::Spi; +use crate::rt::ExceptionFrame; +use cortex_m::asm; + +/// SPI mode +pub const MODE: Mode = Mode { + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, +}; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + + // TRY the other clock configuration + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + let clocks = rcc + .cfgr + .sysclk(80.MHz()) + .pclk1(80.MHz()) + .pclk2(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // let mut nss = gpiob + // .pb0 + // .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper); + + let mut dc = gpiob + .pb1 + .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper); + + // The `L3gd20` abstraction exposed by the `f3` crate requires a specific pin configuration to + // be used and won't accept any configuration other than the one used here. Trying to use a + // different pin configuration will result in a compiler error. + let sck = gpioa + .pa5 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let miso = gpioa + .pa6 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + let mosi = gpioa + .pa7 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl); + + // nss.set_high(); + dc.set_low(); + + let mut spi = Spi::spi1( + p.SPI1, + (sck, miso, mosi), + MODE, + // 1.MHz(), + 100.kHz(), + clocks, + &mut rcc.apb2, + ); + + // nss.set_low(); + let data = [0x3C]; + spi.write(&data).unwrap(); + spi.write(&data).unwrap(); + spi.write(&data).unwrap(); + // nss.set_high(); + + // when you reach this breakpoint you'll be able to inspect the variable `_m` which contains the + // gyroscope and the temperature sensor readings + asm::bkpt(); + + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/timer.rs b/stm32l4xx-hal/examples/timer.rs new file mode 100644 index 0000000..0fa7002 --- /dev/null +++ b/stm32l4xx-hal/examples/timer.rs @@ -0,0 +1,61 @@ +//! Blinks an LED + +#![no_std] +#![no_main] + +extern crate cortex_m; +#[macro_use] +extern crate cortex_m_rt as rt; +extern crate cortex_m_semihosting as sh; +extern crate panic_semihosting; +extern crate stm32l4xx_hal as hal; + +use crate::hal::interrupt; +use crate::hal::prelude::*; +use crate::hal::timer::{Event, Timer}; +use crate::rt::entry; +use crate::rt::ExceptionFrame; +use cortex_m::peripheral::NVIC; + +use crate::sh::hio; +use core::fmt::Write; + +#[entry] +fn main() -> ! { + let mut hstdout = hio::hstdout().unwrap(); + writeln!(hstdout, "Hello, world!").unwrap(); + + // let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); // .constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + // Try a different clock configuration + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + // let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + // let mut led = gpiob.pb3.into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper); + + unsafe { NVIC::unmask(hal::stm32::Interrupt::TIM7) }; + let mut timer = Timer::tim6(dp.TIM6, 1.Hz(), clocks, &mut rcc.apb1r1); + timer.listen(Event::TimeOut); + + loop { + continue; + } +} + +#[interrupt] +fn TIM7() { + static mut COUNT: u32 = 0; + *COUNT += 1; + // let mut hstdout = hio::hstdout().unwrap(); + // writeln!(hstdout, "Hello, TIM!").unwrap(); +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/touch.rs b/stm32l4xx-hal/examples/touch.rs new file mode 100644 index 0000000..2ced024 --- /dev/null +++ b/stm32l4xx-hal/examples/touch.rs @@ -0,0 +1,73 @@ +//! Test the serial interface +//! +//! This example requires you to short (connect) the TX and RX pins. +#![no_main] +#![no_std] + +extern crate cortex_m; +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; + +extern crate stm32l4xx_hal as hal; + +use crate::hal::prelude::*; +use crate::hal::tsc::Tsc; +use crate::rt::ExceptionFrame; + +#[entry] +fn main() -> ! { + let p = hal::stm32::Peripherals::take().unwrap(); + // let cp = cortex_m::Peripherals::take().unwrap(); + + let mut flash = p.FLASH.constrain(); + let mut rcc = p.RCC.constrain(); + let mut pwr = p.PWR.constrain(&mut rcc.apb1r1); + // let mut gpioa = p.GPIOA.split(&mut rcc.ahb2); + let mut gpiob = p.GPIOB.split(&mut rcc.ahb2); + + // clock configuration using the default settings (all clocks run at 8 MHz) + let _clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + // TRY this alternate clock configuration (clocks run at nearly the maximum frequency) + // let clocks = rcc.cfgr.sysclk(64.MHz()).pclk1(32.MHz()).freeze(&mut flash.acr); + + // let mut delay = Delay::new(cp.SYST, clocks); + let mut led = gpiob + .pb3 + .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper); + + let sample_pin = + gpiob + .pb4 + .into_alternate_open_drain(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + let mut c1 = gpiob + .pb5 + .into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + let mut c2 = gpiob + .pb6 + .into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + // let mut c3 = gpiob.pb7.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + + // , (c1, c2, c3) + let tsc = Tsc::tsc(p.TSC, sample_pin, &mut rcc.ahb1, None); + + let baseline = tsc.acquire(&mut c1).unwrap(); + let threshold = (baseline / 100) * 60; + + loop { + let touched = tsc.acquire(&mut c1).unwrap(); + let _touched_c2 = tsc.acquire(&mut c2).unwrap(); + // try and pass c1, it will detect an error! + let _touched_c2_again = tsc.read(&mut c2).unwrap(); + if touched < threshold { + led.set_high(); + } else { + led.set_low(); + } + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/examples/usb_serial.rs b/stm32l4xx-hal/examples/usb_serial.rs new file mode 100644 index 0000000..39c3092 --- /dev/null +++ b/stm32l4xx-hal/examples/usb_serial.rs @@ -0,0 +1,117 @@ +//! CDC-ACM serial port example using polling in a busy loop. +//! Target board: NUCLEO-L432KC +#![no_std] +#![no_main] + +extern crate panic_semihosting; + +use cortex_m_rt::entry; +use stm32l4xx_hal::usb::{Peripheral, UsbBus}; +use stm32l4xx_hal::{prelude::*, stm32}; +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +fn enable_crs() { + let rcc = unsafe { &(*stm32::RCC::ptr()) }; + rcc.apb1enr1.modify(|_, w| w.crsen().set_bit()); + let crs = unsafe { &(*stm32::CRS::ptr()) }; + // Initialize clock recovery + // Set autotrim enabled. + crs.cr.modify(|_, w| w.autotrimen().set_bit()); + // Enable CR + crs.cr.modify(|_, w| w.cen().set_bit()); +} + +/// Enables VddUSB power supply +fn enable_usb_pwr() { + // Enable PWR peripheral + let rcc = unsafe { &(*stm32::RCC::ptr()) }; + rcc.apb1enr1.modify(|_, w| w.pwren().set_bit()); + + // Enable VddUSB + let pwr = unsafe { &*stm32::PWR::ptr() }; + pwr.cr2.modify(|_, w| w.usv().set_bit()); +} + +#[entry] +fn main() -> ! { + let dp = stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + let _clocks = rcc + .cfgr + .hsi48(true) + .sysclk(80.MHz()) + .freeze(&mut flash.acr, &mut pwr); + + enable_crs(); + + // disable Vddusb power isolation + enable_usb_pwr(); + + // Configure the on-board LED (LD3, green) + let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + let mut led = gpiob + .pb3 + .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper); + led.set_low(); // Turn off + + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + + let usb = Peripheral { + usb: dp.USB, + pin_dm: gpioa + .pa11 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh), + pin_dp: gpioa + .pa12 + .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh), + }; + let usb_bus = UsbBus::new(usb); + + let mut serial = SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(USB_CLASS_CDC) + .build(); + + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + led.set_high(); // Turn on + + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + } + _ => {} + } + } + } + _ => {} + } + + led.set_low(); // Turn off + } +} diff --git a/stm32l4xx-hal/examples/watchdog.rs b/stm32l4xx-hal/examples/watchdog.rs new file mode 100644 index 0000000..bc42591 --- /dev/null +++ b/stm32l4xx-hal/examples/watchdog.rs @@ -0,0 +1,59 @@ +//! Example of watchdog timer +#![no_std] +#![no_main] + +use crate::hal::delay::Delay; +use crate::hal::prelude::*; +use crate::hal::watchdog::IndependentWatchdog; +use cortex_m_rt::{entry, exception, ExceptionFrame}; +use cortex_m_semihosting as sh; +use panic_semihosting as _; +use stm32l4xx_hal as hal; + +use crate::sh::hio; +use core::fmt::Write; + +#[entry] +fn main() -> ! { + let mut hstdout = hio::hstdout().unwrap(); + + writeln!(hstdout, "Hello, world!").unwrap(); + + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + + // Try a different clock configuration + let clocks = rcc.cfgr.lsi(true).freeze(&mut flash.acr, &mut pwr); + + let mut timer = Delay::new(cp.SYST, clocks); + + // Initiate the independent watchdog timer + let mut watchdog = IndependentWatchdog::new(dp.IWDG); + watchdog.stop_on_debug(&dp.DBGMCU, true); + + // Start the independent watchdog timer + watchdog.start(1020.millis()); + timer.delay_ms(1000_u32); + + // Feed the independent watchdog timer + watchdog.feed(); + timer.delay_ms(1000_u32); + + watchdog.feed(); + timer.delay_ms(1000_u32); + + watchdog.feed(); + writeln!(hstdout, "Good bye!").unwrap(); + loop { + continue; + } +} + +#[exception] +unsafe fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} diff --git a/stm32l4xx-hal/memory.x b/stm32l4xx-hal/memory.x new file mode 100644 index 0000000..fdf940d --- /dev/null +++ b/stm32l4xx-hal/memory.x @@ -0,0 +1,23 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + /* TODO Adjust these memory regions to match your device memory layout */ + FLASH : ORIGIN = 0x8000000, LENGTH = 128K + RAM : ORIGIN = 0x20000000, LENGTH = 32K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* You may want to use this variable to locate the call stack and static + variables in different memory regions. Below is shown the default value */ +/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ + +/* You can use this symbol to customize the location of the .text section */ +/* If omitted the .text section will be placed right after the .vector_table + section */ +/* This is required only on microcontrollers that store some configuration right + after the vector table */ +/* _stext = ORIGIN(FLASH) + 0x400; */ + +/* Size of the heap (in bytes) */ +/* _heap_size = 1024; */ diff --git a/stm32l4xx-hal/src/adc.rs b/stm32l4xx-hal/src/adc.rs new file mode 100644 index 0000000..4d23c13 --- /dev/null +++ b/stm32l4xx-hal/src/adc.rs @@ -0,0 +1,702 @@ +//! # Analog to Digital converter + +use core::{ + convert::Infallible, + ops::DerefMut, + sync::atomic::{self, Ordering}, +}; + +use crate::{ + dma::{dma1, Event as DMAEvent, RxDma, Transfer, TransferPayload, W}, + dmamux::{DmaInput, DmaMux}, + gpio::{self, Analog}, + hal::{ + adc::{Channel as EmbeddedHalChannel, OneShot}, + blocking::delay::DelayUs, + }, + pac, + rcc::{Enable, Reset, AHB2, CCIPR}, + signature::{VrefCal, VtempCalHigh, VtempCalLow, VDDA_CALIB_MV}, +}; + +use pac::{ADC1, ADC_COMMON}; +use stable_deref_trait::StableDeref; + +/// Vref internal signal, used for calibration +pub struct Vref; + +/// Vbat internal signal, used for monitoring the battery +pub struct Vbat; + +/// Core temperature internal signal +pub struct Temperature; + +/// Analog to Digital converter interface +pub struct ADC { + pub(crate) adc: ADC1, + common: ADC_COMMON, + resolution: Resolution, + sample_time: SampleTime, + calibrated_vdda: u32, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum DmaMode { + Disabled = 0, + Oneshot = 1, + // FIXME: Figure out how to get circular DMA to function properly (requires circbuffer?) + // Circular = 2, +} + +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub enum Sequence { + One = 0, + Two = 1, + Three = 2, + Four = 3, + Five = 4, + Six = 5, + Seven = 6, + Eight = 7, + Nine = 8, + Ten = 9, + Eleven = 10, + Twelve = 11, + Thirteen = 12, + Fourteen = 13, + Fifteen = 14, + Sixteen = 15, +} + +impl From for Sequence { + fn from(bits: u8) -> Self { + match bits { + 0 => Sequence::One, + 1 => Sequence::Two, + 2 => Sequence::Three, + 3 => Sequence::Four, + 4 => Sequence::Five, + 5 => Sequence::Six, + 6 => Sequence::Seven, + 7 => Sequence::Eight, + 8 => Sequence::Nine, + 9 => Sequence::Ten, + 10 => Sequence::Eleven, + 11 => Sequence::Twelve, + 12 => Sequence::Thirteen, + 13 => Sequence::Fourteen, + 14 => Sequence::Fifteen, + 15 => Sequence::Sixteen, + _ => unimplemented!(), + } + } +} + +impl Into for Sequence { + fn into(self) -> u8 { + match self { + Sequence::One => 0, + Sequence::Two => 1, + Sequence::Three => 2, + Sequence::Four => 3, + Sequence::Five => 4, + Sequence::Six => 5, + Sequence::Seven => 6, + Sequence::Eight => 7, + Sequence::Nine => 8, + Sequence::Ten => 9, + Sequence::Eleven => 10, + Sequence::Twelve => 11, + Sequence::Thirteen => 12, + Sequence::Fourteen => 13, + Sequence::Fifteen => 14, + Sequence::Sixteen => 15, + } + } +} + +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub enum Event { + EndOfRegularSequence, + EndOfRegularConversion, +} + +impl ADC { + /// Initialize the ADC + pub fn new( + adc: ADC1, + common: ADC_COMMON, + ahb: &mut AHB2, + ccipr: &mut CCIPR, + delay: &mut impl DelayUs, + ) -> Self { + // Enable peripheral + ADC1::enable(ahb); + + // Reset peripheral + ADC1::reset(ahb); + + // Select system clock as ADC clock source + ccipr.ccipr().modify(|_, w| { + // This is sound, as `0b11` is a valid value for this field. + unsafe { + w.adcsel().bits(0b11); + } + + w + }); + + // Initialize the ADC, according to the STM32L4xx Reference Manual, + // section 16.4.6. + adc.cr.write(|w| w.deeppwd().clear_bit()); // exit deep-power-down mode + adc.cr.modify(|_, w| w.advregen().set_bit()); // enable internal voltage regulator + + // According to the STM32L4xx Reference Manual, section 16.4.6, we need + // to wait for T_ADCVREG_STUP after enabling the internal voltage + // regulator. For the STM32L433, this is 20 us. We choose 25 us to + // account for bad clocks. + delay.delay_us(25); + + // Calibration procedure according to section 16.4.8. + adc.cr.modify(|_, w| { + w.adcal().set_bit(); // start calibration + w.adcaldif().clear_bit(); // single-ended mode + + w + }); + + while adc.cr.read().adcal().bit_is_set() {} + + // We need to wait 4 ADC clock after ADCAL goes low, 1 us is more than enough + delay.delay_us(1); + + let mut s = Self { + adc, + common, + resolution: Resolution::default(), + sample_time: SampleTime::default(), + calibrated_vdda: VDDA_CALIB_MV, + }; + + // Temporarily enable Vref + let mut vref = s.enable_vref(delay); + + s.calibrate(&mut vref); + + s.common.ccr.modify(|_, w| w.vrefen().clear_bit()); + s + } + + /// Enable and get the `Vref` + pub fn enable_vref(&mut self, delay: &mut impl DelayUs) -> Vref { + self.common.ccr.modify(|_, w| w.vrefen().set_bit()); + + // "Table 24. Embedded internal voltage reference" states that it takes a maximum of 12 us + // to stabilize the internal voltage reference, we wait a little more. + delay.delay_us(15); + + Vref {} + } + + /// Enable and get the `Temperature` + pub fn enable_temperature(&mut self, delay: &mut impl DelayUs) -> Temperature { + self.common.ccr.modify(|_, w| w.ch17sel().set_bit()); + + // FIXME: This note from the reference manual is currently not possible + // rm0351 section 18.4.32 pg580 (L47/L48/L49/L4A models) + // Note: + // The sensor has a startup time after waking from power-down mode before it can output VTS + // at the correct level. The ADC also has a startup time after power-on, so to minimize the + // delay, the ADEN and CH17SEL bits should be set at the same time. + // + // https://github.com/STMicroelectronics/STM32CubeL4/blob/master/Drivers/STM32L4xx_HAL_Driver/Inc/stm32l4xx_ll_adc.h#L1363 + // 120us is used in the ST HAL code + delay.delay_us(150); + + Temperature {} + } + + /// Enable and get the `Vbat` + pub fn enable_vbat(&mut self) -> Vbat { + self.common.ccr.modify(|_, w| w.ch18sel().set_bit()); + + Vbat {} + } + + /// Calculates the system VDDA by sampling the internal VREF channel and comparing + /// the result with the value stored at the factory. If the chip's VDDA is not stable, run + /// this before each ADC conversion. + pub fn calibrate(&mut self, vref: &mut Vref) { + let vref_cal = VrefCal::get().read(); + let old_sample_time = self.sample_time; + + // "Table 24. Embedded internal voltage reference" states that the sample time needs to be + // at a minimum 4 us. With 640.5 ADC cycles we have a minimum of 8 us at 80 MHz, leaving + // some headroom. + self.set_sample_time(SampleTime::Cycles640_5); + + // This can't actually fail, it's just in a result to satisfy hal trait + let vref_samp = self.read(vref).unwrap(); + + self.set_sample_time(old_sample_time); + + // Safety: DIV by 0 is possible if vref_samp is 0 + self.calibrated_vdda = (VDDA_CALIB_MV * u32::from(vref_cal)) / u32::from(vref_samp); + } + + /// Set the ADC resolution + pub fn set_resolution(&mut self, resolution: Resolution) { + self.resolution = resolution; + } + + /// Set the sample time + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + /// Get the max value for the current resolution + pub fn get_max_value(&self) -> u16 { + match self.resolution { + Resolution::Bits12 => 4095, + Resolution::Bits10 => 1023, + Resolution::Bits8 => 255, + Resolution::Bits6 => 63, + } + } + + /// Release the ADC peripheral + /// + /// Drops `ADC` and returns the `(pac::ADC, pad::ADC_COMMON)` that is was wrapping, giving the + /// user full access to the peripheral. + pub fn release(self) -> (ADC1, ADC_COMMON) { + (self.adc, self.common) + } + + /// Convert a measurement to millivolts + pub fn to_millivolts(&self, sample: u16) -> u16 { + ((u32::from(sample) * self.calibrated_vdda) / self.resolution.to_max_count()) as u16 + } + + /// Convert a raw sample from the `Temperature` to deg C + pub fn to_degrees_centigrade(&self, sample: u16) -> f32 { + let sample = (u32::from(sample) * self.calibrated_vdda) / VDDA_CALIB_MV; + (VtempCalHigh::TEMP_DEGREES - VtempCalLow::TEMP_DEGREES) as f32 + // as signed because RM0351 doesn't specify against this being an + // inverse relation (which would result in a negative differential) + / (VtempCalHigh::get().read() as i32 - VtempCalLow::get().read() as i32) as f32 + // this can definitely be negative so must be done as a signed value + * (sample as i32 - VtempCalLow::get().read() as i32) as f32 + // while it would make sense for this to be `VtempCalLow::TEMP_DEGREES` (which is 30*C), + // the RM specifically uses 30*C so this will too + + 30.0 + } + + // DMA channels: + // ADC1: DMA2_3 with C2S 0000 + // ADC2: DMA2_4 with C2S 0000 + // ADC1: DMA1_1 with C1S 0000 (implemented) + // ADC2: DMA1_2 with C1S 0000 + + pub fn get_data(&self) -> u16 { + // Sound, as bits 31:16 are reserved, read-only and 0 in ADC_DR + self.adc.dr.read().bits() as u16 + } + + /// Configure the channel for a specific step in the sequence. + /// + /// Automatically sets the sequence length to the farthes sequence + /// index that has been used so far. Use [`ADC::reset_sequence`] to + /// reset the sequence length. + pub fn configure_sequence( + &mut self, + channel: &mut C, + sequence: Sequence, + sample_time: SampleTime, + ) where + C: Channel, + { + let channel_bits = C::channel(); + channel.set_sample_time(&self.adc, sample_time); + + unsafe { + // This is sound as channel() always returns a valid channel number + match sequence { + Sequence::One => self.adc.sqr1.modify(|_, w| w.sq1().bits(channel_bits)), + Sequence::Two => self.adc.sqr1.modify(|_, w| w.sq2().bits(channel_bits)), + Sequence::Three => self.adc.sqr1.modify(|_, w| w.sq3().bits(channel_bits)), + Sequence::Four => self.adc.sqr1.modify(|_, w| w.sq4().bits(channel_bits)), + Sequence::Five => self.adc.sqr2.modify(|_, w| w.sq5().bits(channel_bits)), + Sequence::Six => self.adc.sqr2.modify(|_, w| w.sq6().bits(channel_bits)), + Sequence::Seven => self.adc.sqr2.modify(|_, w| w.sq7().bits(channel_bits)), + Sequence::Eight => self.adc.sqr2.modify(|_, w| w.sq8().bits(channel_bits)), + Sequence::Nine => self.adc.sqr2.modify(|_, w| w.sq9().bits(channel_bits)), + Sequence::Ten => self.adc.sqr3.modify(|_, w| w.sq10().bits(channel_bits)), + Sequence::Eleven => self.adc.sqr3.modify(|_, w| w.sq11().bits(channel_bits)), + Sequence::Twelve => self.adc.sqr3.modify(|_, w| w.sq12().bits(channel_bits)), + Sequence::Thirteen => self.adc.sqr3.modify(|_, w| w.sq13().bits(channel_bits)), + Sequence::Fourteen => self.adc.sqr3.modify(|_, w| w.sq14().bits(channel_bits)), + Sequence::Fifteen => self.adc.sqr4.modify(|_, w| w.sq15().bits(channel_bits)), + Sequence::Sixteen => self.adc.sqr4.modify(|_, w| w.sq16().bits(channel_bits)), + } + } + + // This will only ever extend the sequence, not shrink it. + let current_seql = self.get_sequence_length(); + let next_seql: u8 = sequence.into(); + if next_seql >= current_seql { + // Note: sequence length of 0 = 1 conversion + self.set_sequence_length(sequence.into()); + } + } + + /// Get the configured sequence length (= `actual sequence length - 1`) + pub(crate) fn get_sequence_length(&self) -> u8 { + self.adc.sqr1.read().l().bits() + } + + /// Private: length must be `actual sequence length - 1`, so not API-friendly. + /// Use [`ADC::reset_sequence`] and [`ADC::configure_sequence`] instead + fn set_sequence_length(&mut self, length: u8) { + self.adc.sqr1.modify(|_, w| unsafe { w.l().bits(length) }); + } + + /// Reset the sequence length to 1 + /// + /// Does *not* erase previously configured sequence settings, only + /// changes the sequence length + pub fn reset_sequence(&mut self) { + self.adc.sqr1.modify(|_, w| unsafe { w.l().bits(0b0000) }) + } + + pub fn has_completed_conversion(&self) -> bool { + self.adc.isr.read().eoc().bit_is_set() + } + + pub fn has_completed_sequence(&self) -> bool { + self.adc.isr.read().eos().bit_is_set() + } + + pub fn clear_end_flags(&mut self) { + // EOS and EOC are reset by setting them (See reference manual section 16.6.1) + self.adc + .isr + .modify(|_, w| w.eos().set_bit().eoc().set_bit()); + } + + pub fn start_conversion(&mut self) { + self.enable(); + self.clear_end_flags(); + self.adc.cr.modify(|_, w| w.adstart().set_bit()); + } + + pub fn is_converting(&self) -> bool { + self.adc.cr.read().adstart().bit_is_set() + } + + pub fn listen(&mut self, event: Event) { + self.adc.ier.modify(|_, w| match event { + Event::EndOfRegularSequence => w.eosie().set_bit(), + Event::EndOfRegularConversion => w.eocie().set_bit(), + }); + } + + pub fn unlisten(&mut self, event: Event) { + self.adc.ier.modify(|_, w| match event { + Event::EndOfRegularSequence => w.eosie().clear_bit(), + Event::EndOfRegularConversion => w.eocie().clear_bit(), + }); + } + + pub fn enable(&mut self) { + if !self.is_enabled() { + // Make sure bits are off + while self.adc.cr.read().addis().bit_is_set() {} + + // Clear ADRDY by setting it (See Reference Manual section 1.16.1) + self.adc.isr.modify(|_, w| w.adrdy().set_bit()); + self.adc.cr.modify(|_, w| w.aden().set_bit()); + while self.adc.isr.read().adrdy().bit_is_clear() {} + + // Configure ADC + self.adc.cfgr.modify(|_, w| { + // This is sound, as all `Resolution` values are valid for this + // field. + unsafe { w.res().bits(self.resolution as u8) } + }); + } + } + + pub fn is_enabled(&self) -> bool { + self.adc.cr.read().aden().bit_is_set() + } + + pub fn disable(&mut self) { + self.adc.cr.modify(|_, w| w.addis().set_bit()); + } +} + +impl OneShot for ADC +where + C: Channel, +{ + type Error = Infallible; + + fn read(&mut self, channel: &mut C) -> nb::Result { + self.configure_sequence(channel, Sequence::One, self.sample_time); + + self.start_conversion(); + while !self.has_completed_sequence() {} + + // Read ADC value first time and discard it, as per errata sheet. + // The errata states that if we do conversions slower than 1 kHz, the + // first read ADC value can be corrupted, so we discard it and measure again. + let _ = self.get_data(); + + self.start_conversion(); + while !self.has_completed_sequence() {} + + // Read ADC value + let val = self.get_data(); + + // Disable ADC + self.disable(); + + Ok(val) + } +} + +impl TransferPayload for RxDma { + fn start(&mut self) { + self.channel.start(); + } + + fn stop(&mut self) { + self.channel.stop(); + } +} + +impl RxDma { + pub fn split(mut self) -> (ADC, dma1::C1) { + self.stop(); + (self.payload, self.channel) + } +} + +impl Transfer> +where + BUFFER: Sized + StableDeref + DerefMut + 'static, +{ + pub fn from_adc_dma( + dma: RxDma, + buffer: BUFFER, + dma_mode: DmaMode, + transfer_complete_interrupt: bool, + ) -> Self { + let (adc, channel) = dma.split(); + Transfer::from_adc(adc, channel, buffer, dma_mode, transfer_complete_interrupt) + } + + /// Initiate a new DMA transfer from an ADC. + /// + /// `dma_mode` indicates the desired mode for DMA. + /// + /// If `transfer_complete_interrupt` is true, the transfer + /// complete interrupt (= `DMA1_CH1`) will be enabled + pub fn from_adc( + mut adc: ADC, + mut channel: dma1::C1, + buffer: BUFFER, + dma_mode: DmaMode, + transfer_complete_interrupt: bool, + ) -> Self { + assert!(dma_mode != DmaMode::Disabled); + + let (enable, circular) = match dma_mode { + DmaMode::Disabled => (false, false), + DmaMode::Oneshot => (true, false), + }; + + adc.adc + .cfgr + .modify(|_, w| w.dmaen().bit(enable).dmacfg().bit(circular)); + + channel.set_peripheral_address(&adc.adc.dr as *const _ as u32, false); + + // SAFETY: since the length of BUFFER is known to be `N`, we are allowed + // to perform N transfers into said buffer + channel.set_memory_address(buffer.as_ptr() as u32, true); + channel.set_transfer_length(N as u16); + + channel.set_request_line(DmaInput::Adc1).unwrap(); + + channel.ccr().modify(|_, w| unsafe { + w.mem2mem() + .clear_bit() + // 00: Low, 01: Medium, 10: High, 11: Very high + .pl() + .bits(0b01) + // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .msize() + .bits(0b01) + // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .psize() + .bits(0b01) + // Peripheral -> Mem + .dir() + .clear_bit() + .circ() + .bit(circular) + }); + + if transfer_complete_interrupt { + channel.listen(DMAEvent::TransferComplete); + } + + atomic::compiler_fence(Ordering::Release); + + channel.start(); + adc.start_conversion(); + + Transfer::w( + buffer, + RxDma { + channel, + payload: adc, + }, + ) + } +} + +/// ADC resolution setting +/// +/// The default setting is 12 bits. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum Resolution { + /// 12-bit resolution + Bits12 = 0b00, + + /// 10-bit resolution + Bits10 = 0b01, + + /// 8-bit resolution + Bits8 = 0b10, + + /// 6-bit resolution + Bits6 = 0b11, +} + +impl Default for Resolution { + fn default() -> Self { + Self::Bits12 + } +} + +impl Resolution { + fn to_max_count(&self) -> u32 { + match self { + Resolution::Bits12 => (1 << 12) - 1, + Resolution::Bits10 => (1 << 10) - 1, + Resolution::Bits8 => (1 << 8) - 1, + Resolution::Bits6 => (1 << 6) - 1, + } + } +} + +/// ADC sample time +/// +/// The default setting is 2.5 ADC clock cycles. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum SampleTime { + /// 2.5 ADC clock cycles + Cycles2_5 = 0b000, + + /// 6.5 ADC clock cycles + Cycles6_5 = 0b001, + + /// 12.5 ADC clock cycles + Cycles12_5 = 0b010, + + /// 24.5 ADC clock cycles + Cycles24_5 = 0b011, + + /// 47.5 ADC clock cycles + Cycles47_5 = 0b100, + + /// 92.5 ADC clock cycles + Cycles92_5 = 0b101, + + /// 247.5 ADC clock cycles + Cycles247_5 = 0b110, + + /// 640.5 ADC clock cycles + Cycles640_5 = 0b111, +} + +impl Default for SampleTime { + fn default() -> Self { + Self::Cycles2_5 + } +} + +/// Implemented for all types that represent ADC channels +pub trait Channel: EmbeddedHalChannel { + fn set_sample_time(&mut self, adc: &ADC1, sample_time: SampleTime); +} + +macro_rules! adc_pins { + ( + $( + $id:expr, + $pin:ty, + $smpr:ident, + $smp:ident; + )* + ) => { + $( + impl EmbeddedHalChannel for $pin { + type ID = u8; + + fn channel() -> Self::ID { + $id + } + } + + impl Channel for $pin { + fn set_sample_time(&mut self, + adc: &ADC1, + sample_time: SampleTime, + ) { + adc.$smpr.modify(|_, w| { + // This is sound, as all `SampleTime` values are valid + // for this field. + unsafe { + w.$smp().bits(sample_time as u8) + } + }) + } + } + )* + }; +} + +adc_pins!( + 0, Vref, smpr1, smp0; + 1, gpio::PC0, smpr1, smp1; + 2, gpio::PC1, smpr1, smp2; + 3, gpio::PC2, smpr1, smp3; + 4, gpio::PC3, smpr1, smp4; + 5, gpio::PA0, smpr1, smp5; + 6, gpio::PA1, smpr1, smp6; + 7, gpio::PA2, smpr1, smp7; + 8, gpio::PA3, smpr1, smp8; + 9, gpio::PA4, smpr1, smp9; + 10, gpio::PA5, smpr2, smp10; + 11, gpio::PA6, smpr2, smp11; + 12, gpio::PA7, smpr2, smp12; + 13, gpio::PC4, smpr2, smp13; + 14, gpio::PC5, smpr2, smp14; + 15, gpio::PB0, smpr2, smp15; + 16, gpio::PB1, smpr2, smp16; + 17, Temperature, smpr2, smp17; + 18, Vbat, smpr2, smp18; +); diff --git a/stm32l4xx-hal/src/can.rs b/stm32l4xx-hal/src/can.rs new file mode 100644 index 0000000..d768ff9 --- /dev/null +++ b/stm32l4xx-hal/src/can.rs @@ -0,0 +1,93 @@ +//! # Controller Area Network (CAN) Interface +//! +//! Based on STM32F4xx HAL. + +use crate::pac::CAN1; +use crate::rcc::{Enable, RccBus, Reset}; + +mod sealed { + pub trait Sealed {} +} + +/// A pair of (TX, RX) pins configured for CAN communication +pub trait Pins: sealed::Sealed { + /// The CAN peripheral that uses these pins + type Instance; +} + +/// Implements sealed::Sealed and Pins for a (TX, RX) pair of pins associated with a CAN peripheral +/// The alternate function number can be specified after each pin name. +macro_rules! pins { + ($($PER:ident => ($tx:ident<$txaf:literal>, $rx:ident<$rxaf:literal>),)+) => { + $( + impl crate::can::sealed::Sealed for ($tx>, $rx>) {} + impl crate::can::Pins for ($tx>, $rx>) { + type Instance = $PER; + } + )+ + } +} + +mod common_pins { + use crate::gpio::{ + gpioa::{PA11, PA12}, + gpiob::{PB8, PB9}, + gpiod::{PD0, PD1}, + PushPull, + }; + use crate::pac::CAN1; + + // All STM32L4 models with CAN support these pins + pins! { + CAN1 => (PA12<9>, PA11<9>), + CAN1 => (PD1<9>, PD0<9>), + CAN1 => (PB9<9>, PB8<9>), + } +} + +// L4x1 +#[cfg(any(feature = "stm32l431", feature = "stm32l451", feature = "stm32l471"))] +mod pb13_pb12_af10 { + use crate::gpio::{ + gpiob::{PB12, PB13}, + PushPull, + }; + use crate::pac::CAN1; + pins! { CAN1 => (PB13<10>, PB12<10>), } +} + +impl crate::can::sealed::Sealed for crate::pac::CAN1 {} + +/// Interface to the CAN peripheral. +pub struct Can { + can: CAN, + pins: Pins, +} + +impl Can +where + CAN: Enable + Reset, + P: Pins, +{ + /// Creates a CAN interface. + pub fn new(apb: &mut ::Bus, can: CAN, pins: P) -> Can { + CAN::enable(apb); + CAN::reset(apb); + Can { can, pins } + } + + // Split the peripheral back into its components. + pub fn split(self) -> (CAN, P) { + (self.can, self.pins) + } +} + +unsafe impl bxcan::Instance for Can { + const REGISTERS: *mut bxcan::RegisterBlock = CAN1::ptr() as *mut _; +} + +unsafe impl bxcan::FilterOwner for Can { + const NUM_FILTER_BANKS: u8 = 14; +} + +unsafe impl bxcan::MasterInstance for Can {} diff --git a/stm32l4xx-hal/src/crc.rs b/stm32l4xx-hal/src/crc.rs new file mode 100644 index 0000000..20bd56c --- /dev/null +++ b/stm32l4xx-hal/src/crc.rs @@ -0,0 +1,208 @@ +//! CRC calculation unit +//! +//! Usage example: +//! ``` +//! let crc = dp.CRC.constrain(&mut rcc.ahb1); +//! +//! // Lets use the CRC-16-CCITT polynomial +//! let mut crc = crc.polynomial(crc::Polynomial::L16(0x1021)).freeze(); +//! +//! let data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; +//! crc.feed(&data); +//! +//! let result = crc.result(); +//! assert!(result == 0x78cb); +//! ``` + +#![deny(missing_docs)] + +use crate::rcc::{self, Enable}; +use crate::stm32::CRC; +use core::hash::Hasher; + +/// Extension trait to constrain the CRC peripheral. +pub trait CrcExt { + /// Constrains the CRC peripheral to play nicely with the other abstractions + fn constrain(self, ahb1: &mut rcc::AHB1) -> Config; +} + +impl CrcExt for CRC { + fn constrain(self, ahb1: &mut rcc::AHB1) -> Config { + // Enable power to CRC unit + CRC::enable(ahb1); + + // Default values + Config { + initial_value: 0xffff_ffff, + polynomial: Polynomial::L32(0x04c1_1db7), + input_bit_reversal: None, + output_bit_reversal: false, + } + } +} + +/// Polynomial settings. +pub enum Polynomial { + /// 7-bit polynomial, only the lowest 7 bits are valid + L7(u8), + /// 8-bit polynomial + L8(u8), + /// 16-bit polynomial + L16(u16), + /// 32-bit polynomial + L32(u32), +} + +/// Bit reversal settings. +pub enum BitReversal { + /// Reverse bits by byte + ByByte, + /// Reverse bits by half-word + ByHalfWord, + /// Reverse bits by word + ByWord, +} + +/// CRC configuration structure, uses builder pattern. +pub struct Config { + initial_value: u32, + polynomial: Polynomial, + input_bit_reversal: Option, + output_bit_reversal: bool, +} + +impl Config { + /// Sets the initial value of the CRC. + pub fn initial_value(mut self, init: u32) -> Self { + self.initial_value = init; + + self + } + + /// Sets the polynomial of the CRC. + pub fn polynomial(mut self, polynomial: Polynomial) -> Self { + self.polynomial = polynomial; + + self + } + + /// Enables bit reversal of the inputs. + pub fn input_bit_reversal(mut self, rev: BitReversal) -> Self { + self.input_bit_reversal = Some(rev); + + self + } + + /// Enables bit reversal of the outputs. + pub fn output_bit_reversal(mut self, rev: bool) -> Self { + self.output_bit_reversal = rev; + + self + } + + /// Freezes the peripheral, making the configuration take effect. + pub fn freeze(self) -> Crc { + let crc = unsafe { &(*CRC::ptr()) }; + + let (poly, poly_bits, init) = match self.polynomial { + Polynomial::L7(val) => ((val & 0x7f) as u32, 0b11, self.initial_value & 0x7f), + Polynomial::L8(val) => (val as u32, 0b10, self.initial_value & 0xff), + Polynomial::L16(val) => (val as u32, 0b01, self.initial_value & 0xffff), + Polynomial::L32(val) => (val, 0b00, self.initial_value), + }; + + let in_rev_bits = match self.input_bit_reversal { + None => 0b00, + Some(BitReversal::ByByte) => 0b01, + Some(BitReversal::ByHalfWord) => 0b10, + Some(BitReversal::ByWord) => 0b11, + }; + + crc.init.write(|w| w.init().bits(init)); + crc.pol.write(|w| unsafe { w.bits(poly) }); + crc.cr.write(|w| { + w.rev_in() + .bits(in_rev_bits) + .polysize() + .bits(poly_bits) + .reset() + .set_bit(); + + if self.output_bit_reversal { + w.rev_out().set_bit() + } else { + w.rev_out().clear_bit() + } + }); + + Crc {} + } +} + +/// Constrained CRC peripheral. +pub struct Crc {} + +impl Crc { + /// This will reset the CRC to its initial condition. + #[inline] + pub fn reset(&mut self) { + let crc = unsafe { &(*CRC::ptr()) }; + + crc.cr.modify(|_, w| w.reset().set_bit()); + } + + /// This will reset the CRC to its initial condition, however with a specific initial value. + /// This is very useful if many task are sharing the CRC peripheral, as one can read out the + /// intermediate result, store it until the next time a task runs, and initialize with the + /// intermediate result to continue where the task left off. + #[inline] + pub fn reset_with_inital_value(&mut self, initial_value: u32) { + let crc = unsafe { &(*CRC::ptr()) }; + + crc.init.write(|w| w.init().bits(initial_value)); + crc.cr.modify(|_, w| w.reset().set_bit()); + } + + /// Feed the CRC with data + #[inline] + pub fn feed(&mut self, data: &[u8]) { + let crc = unsafe { &(*CRC::ptr()) }; + for &byte in data { + crc.dr8().write(|w| w.dr8().bits(byte)); + } + } + + /// Get the result of the CRC, depending on the polynomial chosen only a certain amount of the + /// bits are the result. This will reset the CRC peripheral after use. + #[inline] + pub fn result(&mut self) -> u32 { + let ret = self.peek_result(); + + self.reset(); + + ret + } + + /// Get a peed at the result of the CRC, depending on the polynomial chosen only a certain + /// amount of the bits are the result. + #[inline] + pub fn peek_result(&self) -> u32 { + let crc = unsafe { &(*CRC::ptr()) }; + + crc.dr().read().bits() + } +} + +impl Hasher for Crc { + #[inline] + fn finish(&self) -> u64 { + // `peek_result` as `core::hash::Hasher` required that the `finish` method does not reset + // the hasher. + self.peek_result() as u64 + } + + #[inline] + fn write(&mut self, data: &[u8]) { + self.feed(data); + } +} diff --git a/stm32l4xx-hal/src/datetime.rs b/stm32l4xx-hal/src/datetime.rs new file mode 100644 index 0000000..8b79d82 --- /dev/null +++ b/stm32l4xx-hal/src/datetime.rs @@ -0,0 +1,146 @@ +//! Date and timer units & helper functions + +pub use fugit::{ + HoursDurationU32 as Hour, MicrosDurationU32 as Micros, MinutesDurationU32 as Minute, + SecsDurationU32 as Second, +}; + +/// Day (1-7) +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Day(pub u32); + +/// Date (1-31) +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct DateInMonth(pub u32); + +/// Week (1-52) +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Week(pub u32); + +/// Month (1-12) +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Month(pub u32); + +/// Year +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Year(pub u32); + +/// Extension trait that adds convenience methods to the `u32` type +pub trait U32Ext { + fn day(self) -> Day; + /// Seconds + fn date(self) -> DateInMonth; + /// Month + fn month(self) -> Month; + /// Year + fn year(self) -> Year; +} + +impl U32Ext for u32 { + fn day(self) -> Day { + Day(self) + } + + fn date(self) -> DateInMonth { + DateInMonth(self) + } + + fn month(self) -> Month { + Month(self) + } + + fn year(self) -> Year { + Year(self) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Time { + pub hours: u32, + pub minutes: u32, + pub seconds: u32, + pub micros: u32, + pub daylight_savings: bool, +} + +impl Time { + pub fn new( + hours: Hour, + minutes: Minute, + seconds: Second, + micros: Micros, + daylight_savings: bool, + ) -> Self { + Self { + hours: hours.ticks(), + minutes: minutes.ticks(), + seconds: seconds.ticks(), + micros: micros.ticks(), + daylight_savings, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Date { + pub day: u32, + pub date: u32, + pub month: u32, + pub year: u32, +} + +impl Date { + pub fn new(day: Day, date: DateInMonth, month: Month, year: Year) -> Self { + Self { + day: day.0, + date: date.0, + month: month.0, + year: year.0, + } + } +} + +macro_rules! impl_from_struct { + ($( + $type:ident: [ $($to:ident),+ ], + )+) => { + $( + $( + impl From <$type> for $to { + fn from(inner: $type) -> $to { + inner.0 as $to + } + } + )+ + )+ + } +} + +macro_rules! impl_to_struct { + ($( + $type:ident: [ $($to:ident),+ ], + )+) => { + $( + $( + impl From <$type> for $to { + fn from(inner: $type) -> $to { + $to(inner as u32) + } + } + )+ + )+ + } +} + +impl_from_struct!( + Day: [u32, u16, u8], + DateInMonth: [u32, u16, u8], + Month: [u32, u16, u8], + Year: [u32, u16, u8], +); + +impl_to_struct!( + u32: [Day, DateInMonth, Month, Year], + u16: [Day, DateInMonth, Month, Year], + u8: [Day, DateInMonth, Month, Year], +); diff --git a/stm32l4xx-hal/src/delay.rs b/stm32l4xx-hal/src/delay.rs new file mode 100644 index 0000000..973cdc9 --- /dev/null +++ b/stm32l4xx-hal/src/delay.rs @@ -0,0 +1,151 @@ +//! Delays + +use cast::u32; +use cortex_m::asm; +use cortex_m::peripheral::syst::SystClkSource; +use cortex_m::peripheral::SYST; + +use crate::hal::blocking::delay::{DelayMs, DelayUs}; +use crate::rcc::Clocks; +use crate::time::Hertz; + +/// System timer (SysTick) as a delay provider +pub struct Delay { + clocks: Clocks, + syst: SYST, +} + +impl Delay { + /// Configures the system timer (SysTick) as a delay provider + pub fn new(mut syst: SYST, clocks: Clocks) -> Self { + syst.set_clock_source(SystClkSource::Core); + + Delay { syst, clocks } + } + + /// Releases the system timer (SysTick) resource + pub fn free(self) -> SYST { + self.syst + } +} + +/// System timer (SysTick) as a delay provider. +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u32) { + self.delay_us(ms * 1_000); + } +} + +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u16) { + self.delay_ms(u32(ms)); + } +} + +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u8) { + self.delay_ms(u32(ms)); + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u32) { + // The SysTick Reload Value register supports values between 1 and 0x00FFFFFF. + const MAX_RVR: u32 = 0x00FF_FFFF; + + let mut total_rvr = us * (self.clocks.hclk().raw() / 1_000_000); + + while total_rvr != 0 { + let current_rvr = if total_rvr <= MAX_RVR { + total_rvr + } else { + MAX_RVR + }; + + self.syst.set_reload(current_rvr); + self.syst.clear_current(); + self.syst.enable_counter(); + + // Update the tracking variable while we are waiting... + total_rvr -= current_rvr; + + while !self.syst.has_wrapped() {} + + self.syst.disable_counter(); + } + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u16) { + self.delay_us(u32(us)) + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u8) { + self.delay_us(u32(us)) + } +} + +/// Cortex-M `asm::delay` as provider +#[derive(Clone, Copy)] +pub struct DelayCM { + sysclk: Hertz, +} + +impl DelayCM { + /// Create a new delay + pub fn new(clocks: Clocks) -> Self { + DelayCM { + sysclk: clocks.sysclk(), + } + } + + /// Create a new delay that is unchecked. The user needs to know the `sysclk`. + /// + /// # Safety + /// Sysclk must be the same as the actual clock frequency of the chip + pub unsafe fn new_unchecked(sysclk: Hertz) -> Self { + DelayCM { sysclk } + } +} + +impl DelayMs for DelayCM { + fn delay_ms(&mut self, ms: u32) { + self.delay_us(ms * 1_000); + } +} + +impl DelayMs for DelayCM { + fn delay_ms(&mut self, ms: u16) { + self.delay_ms(u32(ms)); + } +} + +impl DelayMs for DelayCM { + fn delay_ms(&mut self, ms: u8) { + self.delay_ms(u32(ms)); + } +} + +impl DelayUs for DelayCM { + fn delay_us(&mut self, us: u32) { + // Max delay is 53_687_091 us at 80 MHz + let ticks = self.sysclk.raw() / 1_000_000; + + asm::delay(ticks * us); + } +} + +impl DelayUs for DelayCM { + fn delay_us(&mut self, us: u16) { + self.delay_us(u32(us)) + } +} + +impl DelayUs for DelayCM { + fn delay_us(&mut self, us: u8) { + self.delay_us(u32(us)) + } +} diff --git a/stm32l4xx-hal/src/dma.rs b/stm32l4xx-hal/src/dma.rs new file mode 100644 index 0000000..946bffc --- /dev/null +++ b/stm32l4xx-hal/src/dma.rs @@ -0,0 +1,1314 @@ +//! Direct Memory Access Engine + +#![allow(dead_code)] + +use core::fmt; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::DerefMut; +use core::ptr; +use core::slice; +use core::sync::atomic::{compiler_fence, Ordering}; +use embedded_dma::{StaticReadBuffer, StaticWriteBuffer}; + +use crate::rcc::AHB1; +use stable_deref_trait::StableDeref; + +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + Overrun, + BufferError, +} + +pub enum Event { + HalfTransfer, + TransferComplete, +} + +pub trait CharacterMatch { + /// Checks to see if the peripheral has detected a character match and + /// clears the flag + fn check_character_match(&mut self, clear: bool) -> bool; +} + +pub trait ReceiverTimeout { + /// Check to see if the peripheral has detected a + /// receiver timeout and clears the flag + fn check_receiver_timeout(&mut self, clear: bool) -> bool; +} + +pub trait OperationError { + /// Check to see if the peripheral has detected some + /// sort of error while performing an operation + fn check_operation_error(&mut self) -> Result; +} + +/// Frame reader "worker", access and handling of frame reads is made through this structure. +pub struct FrameReader +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, +{ + buffer: BUFFER, + payload: PAYLOAD, + matching_character: u8, +} + +impl FrameReader +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, +{ + pub(crate) fn new( + buffer: BUFFER, + payload: PAYLOAD, + matching_character: u8, + ) -> FrameReader { + Self { + buffer, + payload, + matching_character, + } + } +} + +impl FrameReader, N> +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, + PAYLOAD: CharacterMatch, +{ + /// Checks to see if the peripheral has detected a character match and + /// clears the flag + pub fn check_character_match(&mut self, clear: bool) -> bool { + self.payload.payload.check_character_match(clear) + } +} + +impl FrameReader, N> +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, + PAYLOAD: ReceiverTimeout, +{ + pub fn check_receiver_timeout(&mut self, clear: bool) -> bool { + self.payload.payload.check_receiver_timeout(clear) + } +} + +impl FrameReader, N> +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, +{ + pub fn check_operation_error(&mut self) -> Result + where + PAYLOAD: OperationError, + { + self.payload.payload.check_operation_error() + } +} + +/// Frame sender "worker", access and handling of frame transmissions is made through this +/// structure. +pub struct FrameSender +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, +{ + buffer: Option, + payload: PAYLOAD, +} + +impl FrameSender +where + BUFFER: Sized + StableDeref> + DerefMut + 'static, +{ + pub(crate) fn new(payload: PAYLOAD) -> FrameSender { + Self { + buffer: None, + payload, + } + } +} + +/// Data type for holding data frames for the Serial. +/// +/// Internally used uninitialized storage, making this storage zero cost to create. It can also be +/// used with, for example, [`heapless::pool`] to create a pool of serial frames. +/// +/// [`heapless::pool`]: https://docs.rs/heapless/0.5.3/heapless/pool/index.html +pub struct DMAFrame { + len: u16, + buf: [MaybeUninit; N], +} + +impl fmt::Debug for DMAFrame { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.read()) + } +} + +impl fmt::Write for DMAFrame { + fn write_str(&mut self, s: &str) -> fmt::Result { + let free = self.free(); + + if s.len() > free { + Err(fmt::Error) + } else { + self.write_slice(s.as_bytes()); + Ok(()) + } + } +} + +impl Default for DMAFrame { + fn default() -> Self { + Self::new() + } +} + +impl DMAFrame { + const INIT: MaybeUninit = MaybeUninit::uninit(); + /// Creates a new node for the Serial DMA + #[inline] + pub const fn new() -> Self { + // Create an uninitialized array of `MaybeUninit`. + Self { + len: 0, + buf: [Self::INIT; N], + } + } + + /// Gives a `&mut [u8]` slice to write into with the maximum size, the `commit` method + /// must then be used to set the actual number of bytes written. + /// + /// Note that this function internally first zeros the uninitialized part of the node's buffer. + pub fn write(&mut self) -> &mut [u8] { + // Initialize remaining memory with a safe value + for elem in &mut self.buf[self.len as usize..] { + *elem = MaybeUninit::new(0); + } + + self.len = self.max_len() as u16; + + // NOTE(unsafe): This is safe as the operation above set the entire buffer to a valid state + unsafe { slice::from_raw_parts_mut(self.buf.as_mut_ptr() as *mut _, self.max_len()) } + } + + /// Used to shrink the current size of the frame, used in conjunction with `write`. + #[inline] + pub fn commit(&mut self, shrink_to: usize) { + // Only shrinking is allowed to remain safe with the `MaybeUninit` + if shrink_to < self.len as _ { + self.len = shrink_to as _; + } + } + + /// Gives an uninitialized `&mut [MaybeUninit]` slice to write into, the `set_len` method + /// must then be used to set the actual number of bytes written. + #[inline] + pub fn write_uninit(&mut self) -> &mut [MaybeUninit; N] { + &mut self.buf + } + + /// Used to set the current size of the frame, used in conjunction with `write_uninit` to have an + /// interface for uninitialized memory. Use with care! + /// + /// # Safety + /// + /// NOTE(unsafe): This must be set so that the final buffer is only referencing initialized + /// memory. + #[inline] + pub unsafe fn set_len(&mut self, len: usize) { + assert!(len <= self.max_len()); + self.len = len as _; + } + + /// Used to write data into the node, and returns how many bytes were written from `buf`. + /// + /// If the node is already partially filled, this will continue filling the node. + pub fn write_slice(&mut self, buf: &[u8]) -> usize { + let count = buf.len().min(self.free()); + + // Used to write data into the `MaybeUninit` + // NOTE(unsafe): Safe based on the size check above + unsafe { + ptr::copy_nonoverlapping( + buf.as_ptr(), + (self.buf.as_mut_ptr() as *mut u8).add(self.len.into()), + count, + ); + } + + self.len += count as u16; + + count + } + + /// Clear the node of all data making it empty + #[inline] + pub fn clear(&mut self) { + self.len = 0; + } + + /// Returns a readable slice which maps to the buffers internal data + #[inline] + pub fn read(&self) -> &[u8] { + // NOTE(unsafe): Safe as it uses the internal length of valid data + unsafe { slice::from_raw_parts(self.buf.as_ptr() as *const _, self.len as usize) } + } + + /// Returns a readable mutable slice which maps to the buffers internal data + #[inline] + pub fn read_mut(&mut self) -> &mut [u8] { + // NOTE(unsafe): Safe as it uses the internal length of valid data + unsafe { slice::from_raw_parts_mut(self.buf.as_mut_ptr() as *mut _, self.len as usize) } + } + + /// Reads how many bytes are available + #[inline] + pub fn len(&self) -> usize { + self.len as usize + } + + /// Reads how many bytes are free + #[inline] + pub fn free(&self) -> usize { + self.max_len() - self.len as usize + } + + /// Get the max length of the frame + #[inline] + pub fn max_len(&self) -> usize { + N + } + + /// Checks if the frame is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + #[inline] + pub(crate) unsafe fn buffer_address_for_dma(&self) -> u32 { + self.buf.as_ptr() as u32 + } + + #[inline] + pub(crate) fn buffer_as_ptr(&self) -> *const MaybeUninit { + self.buf.as_ptr() + } + + #[inline] + pub(crate) fn buffer_as_mut_ptr(&mut self) -> *mut MaybeUninit { + self.buf.as_mut_ptr() + } +} + +impl AsRef<[u8]> for DMAFrame { + #[inline] + fn as_ref(&self) -> &[u8] { + self.read() + } +} + +pub struct CircBuffer +where + BUFFER: 'static, +{ + buffer: &'static mut BUFFER, + payload: PAYLOAD, + read_index: usize, + write_previous: usize, +} + +impl CircBuffer +where + &'static mut BUFFER: StaticWriteBuffer, + BUFFER: 'static, +{ + pub(crate) fn new(buf: &'static mut BUFFER, payload: PAYLOAD) -> Self { + CircBuffer { + buffer: buf, + payload, + read_index: 0, + write_previous: 0, + } + } +} + +pub trait DmaExt { + type Channels; + + fn split(self, ahb: &mut AHB1) -> Self::Channels; +} + +pub trait TransferPayload { + fn start(&mut self); + fn stop(&mut self); +} + +pub struct Transfer +where + PAYLOAD: TransferPayload, +{ + _mode: PhantomData, + buffer: BUFFER, + payload: PAYLOAD, +} + +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn r(buffer: BUFFER, payload: PAYLOAD) -> Self { + Transfer { + _mode: PhantomData, + buffer, + payload, + } + } +} + +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn w(buffer: BUFFER, payload: PAYLOAD) -> Self { + Transfer { + _mode: PhantomData, + buffer, + payload, + } + } +} + +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn rw(buffer: BUFFER, payload: PAYLOAD) -> Self { + Transfer { + _mode: PhantomData, + buffer, + payload, + } + } +} + +impl Drop for Transfer +where + PAYLOAD: TransferPayload, +{ + fn drop(&mut self) { + self.payload.stop(); + compiler_fence(Ordering::SeqCst); + } +} + +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn extract_inner_without_drop(self) -> (BUFFER, PAYLOAD) { + // `Transfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + // We cannot use mem::replace as we do not have valid objects to replace with + let buffer = ptr::read(&self.buffer); + let payload = ptr::read(&self.payload); + core::mem::forget(self); + (buffer, payload) + } + } +} + +/// Read transfer +pub struct R; + +/// Write transfer +pub struct W; + +/// Read/Write transfer +pub struct RW; + +macro_rules! for_all_pairs { + ($mac:ident: $($x:ident)*) => { + // Duplicate the list + for_all_pairs!(@inner $mac: $($x)*; $($x)*); + }; + + // The end of iteration: we exhausted the list + (@inner $mac:ident: ; $($x:ident)*) => {}; + + // The head/tail recursion: pick the first element of the first list + // and recursively do it for the tail. + (@inner $mac:ident: $head:ident $($tail:ident)*; $($x:ident)*) => { + $( + $mac!($head $x); + )* + for_all_pairs!(@inner $mac: $($tail)*; $($x)*); + }; +} + +macro_rules! rx_tx_channel_mapping { + ($CH_A:ident $CH_B:ident) => { + impl Transfer> + where + RxTxDma: TransferPayload, + { + pub fn is_done(&self) -> bool { + !self.payload.rx_channel.in_progress() && !self.payload.tx_channel.in_progress() + } + + pub fn wait(mut self) -> (BUFFER, RxTxDma) { + // XXX should we check for transfer errors here? + // The manual says "A DMA transfer error can be generated by reading + // from or writing to a reserved address space". I think it's impossible + // to get to that state with our type safe API and *safe* Rust. + while !self.is_done() {} + + self.payload.stop(); + + // TODO can we weaken this compiler barrier? + // NOTE(compiler_fence) operations on `buffer` should not be reordered + // before the previous statement, which marks the DMA transfer as done + atomic::compiler_fence(Ordering::SeqCst); + + // `Transfer` has a `Drop` implementation because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields directly. + self.extract_inner_without_drop() + } + } + + impl Transfer> + where + RxTxDma: TransferPayload, + { + pub fn peek(&self) -> &[T] + where + BUFFER: AsRef<[T]>, + { + let pending = self.payload.rx_channel.get_cndtr() as usize; + + let capacity = self.buffer.as_ref().len(); + + &self.buffer.as_ref()[..(capacity - pending)] + } + } + }; +} + +macro_rules! dma { + ($($DMAX:ident: ($dmaX:ident, { + $($CX:ident: ( + $ccrX:ident, + $CCRX:ident, + $cndtrX:ident, + $CNDTRX:ident, + $cparX:ident, + $CPARX:ident, + $cmarX:ident, + $CMARX:ident, + $htifX:ident, + $tcifX:ident, + $chtifX:ident, + $ctcifX:ident, + $cgifX:ident, + $teifX:ident, + $cteifX:ident + ),)+ + }),)+) => { + $( + pub mod $dmaX { + use core::sync::atomic::{self, Ordering}; + use crate::stm32::{$DMAX, dma1}; + use core::ops::DerefMut; + use core::ptr; + use stable_deref_trait::StableDeref; + + use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Transfer, W, R, RW, RxDma, RxTxDma, TxDma, TransferPayload}; + use crate::rcc::{AHB1, Enable}; + + #[allow(clippy::manual_non_exhaustive)] + pub struct Channels((), $(pub $CX),+); + + for_all_pairs!(rx_tx_channel_mapping: $($CX)+); + + $( + /// A singleton that represents a single DMAx channel (channel X in this case) + /// + /// This singleton has exclusive access to the registers of the DMAx channel X + pub struct $CX; + + impl $CX { + /// Associated peripheral `address` + /// + /// `inc` indicates whether the address will be incremented after every transfer + #[inline] + pub fn set_peripheral_address(&mut self, address: u32, inc: bool) { + self.cpar().write(|w| + unsafe { w.pa().bits(address) } + ); + self.ccr().modify(|_, w| w.pinc().bit(inc) ); + } + + /// `address` where from/to data will be read/write + /// + /// `inc` indicates whether the address will be incremented after every transfer + #[inline] + pub fn set_memory_address(&mut self, address: u32, inc: bool) { + self.cmar().write(|w| + unsafe { w.ma().bits(address) } + ); + self.ccr().modify(|_, w| w.minc().bit(inc) ); + } + + /// The amount of transfers that makes up one transaction + #[inline] + pub fn set_transfer_length(&mut self, len: u16) { + self.cndtr().write(|w| w.ndt().bits(len)); + } + + /// Starts the DMA transfer + #[inline] + pub fn start(&mut self) { + self.ccr().modify(|_, w| w.en().set_bit() ); + } + + /// Stops the DMA transfer + #[inline] + pub fn stop(&mut self) { + self.ifcr().write(|w| w.$cgifX().set_bit()); + self.ccr().modify(|_, w| w.en().clear_bit() ); + } + + /// Returns `true` if there's a transfer in progress + #[inline] + pub fn in_progress(&self) -> bool { + self.isr().$tcifX().bit_is_clear() + } + + #[inline] + pub fn listen(&mut self, event: Event) { + match event { + Event::HalfTransfer => self.ccr().modify(|_, w| w.htie().set_bit()), + Event::TransferComplete => { + self.ccr().modify(|_, w| w.tcie().set_bit()) + } + } + } + + #[inline] + pub fn unlisten(&mut self, event: Event) { + match event { + Event::HalfTransfer => { + self.ccr().modify(|_, w| w.htie().clear_bit()) + }, + Event::TransferComplete => { + self.ccr().modify(|_, w| w.tcie().clear_bit()) + } + } + } + + #[inline] + pub(crate) fn isr(&self) -> dma1::isr::R { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*$DMAX::ptr()).isr.read() } + } + + #[inline] + pub(crate) fn ifcr(&self) -> &dma1::IFCR { + unsafe { &(*$DMAX::ptr()).ifcr } + } + + #[inline] + pub(crate) fn ccr(&mut self) -> &dma1::$CCRX { + unsafe { &(*$DMAX::ptr()).$ccrX } + } + + #[inline] + pub(crate) fn cndtr(&mut self) -> &dma1::$CNDTRX { + unsafe { &(*$DMAX::ptr()).$cndtrX } + } + + #[inline] + pub(crate) fn cpar(&mut self) -> &dma1::$CPARX { + unsafe { &(*$DMAX::ptr()).$cparX } + } + + #[inline] + pub(crate) fn cmar(&mut self) -> &dma1::$CMARX { + unsafe { &(*$DMAX::ptr()).$cmarX } + } + + #[cfg(not(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" + )))] + #[inline] + pub(crate) fn cselr(&mut self) -> &dma1::CSELR { + unsafe { &(*$DMAX::ptr()).cselr } + } + + #[inline] + pub(crate) fn get_cndtr(&self) -> u32 { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*$DMAX::ptr()).$cndtrX.read().bits() } + } + + } + + impl FrameSender, N> + where + BUFFER: Sized + StableDeref> + DerefMut + 'static, + TxDma: TransferPayload, + { + /// This method should be called in the transfer complete interrupt of the + /// DMA, will return the sent frame if the transfer was truly completed. + pub fn transfer_complete_interrupt( + &mut self, + ) -> Option { + + // Clear ISR flag (Transfer Complete) + if !self.payload.channel.in_progress() { + self.payload.channel.ifcr().write(|w| w.$ctcifX().set_bit()); + } else { + // The old transfer is not complete + return None; + } + + self.payload.channel.stop(); + + // NOTE(compiler_fence) operations on the DMA should not be reordered + // before the next statement, takes the buffer from the DMA transfer. + atomic::compiler_fence(Ordering::SeqCst); + + // Return the old buffer for the user to do what they want with it + self.buffer.take() + } + + /// Returns `true` if there is an ongoing transfer. + #[inline] + pub fn ongoing_transfer(&self) -> bool { + self.buffer.is_some() + } + + /// Send a frame. Will return `Err(frame)` if there was already an ongoing + /// transaction or if the buffer has not been read out. + pub fn send( + &mut self, + frame: BUFFER, + ) -> Result<(), BUFFER> { + if self.ongoing_transfer() { + // The old transfer is not complete + return Err(frame); + } + + let new_buf = &*frame; + self.payload.channel.set_memory_address(new_buf.buffer_as_ptr() as u32, true); + self.payload.channel.set_transfer_length(new_buf.len() as u16); + + // If there has been an error, clear the error flag to let the next + // transaction start + if self.payload.channel.isr().$teifX().bit_is_set() { + self.payload.channel.ifcr().write(|w| w.$cteifX().set_bit()); + } + + // NOTE(compiler_fence) operations on `buffer` should not be reordered after + // the next statement, which starts the DMA transfer + atomic::compiler_fence(Ordering::Release); + + self.payload.channel.start(); + + self.buffer = Some(frame); + + Ok(()) + } + } + + impl FrameReader, N> + where + BUFFER: Sized + StableDeref> + DerefMut + 'static, + RxDma: TransferPayload, + { + /// This function should be called from the transfer complete interrupt of + /// the corresponding DMA channel. + /// + /// Returns the full buffer received by the USART. + #[inline] + pub fn transfer_complete_interrupt(&mut self, next_frame: BUFFER) -> BUFFER { + self.internal_interrupt(next_frame, false) + } + + /// This function should be called from the character match interrupt of + /// the corresponding USART + /// + /// Returns the buffer received by the USART, including the matching + /// character. + #[inline] + pub fn character_match_interrupt(&mut self, next_frame: BUFFER) -> BUFFER { + self.internal_interrupt(next_frame, true) + } + + /// This function should be called from the receiver timeout interrupt of + /// the corresponding USART + /// + /// Returns the buffer received by the USART. + #[inline] + pub fn receiver_timeout_interrupt(&mut self, next_frame: BUFFER) -> BUFFER { + self.internal_interrupt(next_frame, false) + } + + fn internal_interrupt( + &mut self, + mut next_frame: BUFFER, + character_match_interrupt: bool, + ) -> BUFFER { + let old_buf = &mut *self.buffer; + let new_buf = &mut *next_frame; + new_buf.clear(); + + // Clear ISR flag (Transfer Complete) + if !self.payload.channel.in_progress() { + self.payload.channel.ifcr().write(|w| w.$ctcifX().set_bit()); + } else if character_match_interrupt { + // 1. If DMA not done and there was a character match interrupt, + // let the DMA flush a little and then halt transfer. + // + // This is to alleviate the race condition between the character + // match interrupt and the DMA memory transfer. + let left_in_buffer = self.payload.channel.get_cndtr() as usize; + + for _ in 0..5 { + let now_left = self.payload.channel.get_cndtr() as usize; + + if left_in_buffer - now_left >= 4 { + // We have gotten 4 extra characters flushed + break; + } + } + } + + self.payload.channel.stop(); + + // NOTE(compiler_fence) operations on `buffer` should not be reordered after + // the next statement, which starts a new DMA transfer + atomic::compiler_fence(Ordering::SeqCst); + + let left_in_buffer = self.payload.channel.get_cndtr() as usize; + let got_data_len = old_buf.max_len() - left_in_buffer; // How many transfers were completed = how many bytes are available + unsafe { + old_buf.set_len(got_data_len); + } + + // 2. Check DMA race condition by finding matched character, and that + // the length is larger than 0 + let len = if character_match_interrupt && got_data_len > 0 { + let search_buf = old_buf.read(); + + // Search from the end + let ch = self.matching_character; + if let Some(pos) = search_buf.iter().rposition(|&x| x == ch) { + pos+1 + } else { + // No character match found + 0 + } + } else { + old_buf.len() + }; + + // 3. Start DMA again + let diff = if len < got_data_len { + // We got some extra characters in the from the new frame, move + // them into the new buffer + let diff = got_data_len - len; + + let new_buf_ptr = new_buf.buffer_as_mut_ptr(); + let old_buf_ptr = old_buf.buffer_as_ptr(); + + // new_buf[0..diff].copy_from_slice(&old_buf[len..got_data_len]); + unsafe { + ptr::copy_nonoverlapping(old_buf_ptr.add(len), new_buf_ptr, diff); + } + + diff + } else { + 0 + }; + + self.payload.channel.set_memory_address(unsafe { new_buf.buffer_as_ptr().add(diff) } as u32, true); + self.payload.channel.set_transfer_length((new_buf.max_len() - diff) as u16); + unsafe { old_buf.set_len(got_data_len - diff) }; + let received_buffer = core::mem::replace(&mut self.buffer, next_frame); + + // NOTE(compiler_fence) operations on `buffer` should not be reordered after + // the next statement, which starts the DMA transfer + atomic::compiler_fence(Ordering::Release); + + self.payload.channel.start(); + + // 4. Return full frame + received_buffer + } + } + + impl CircBuffer> + where + RxDma: TransferPayload, + { + /// Determines if the write index passed the given `mark` when it moved + /// from `previous` to `current` with wrapping behaviour at `wrap`. + /// When `current` reaches `mark` (`current == mark`), this method already + /// returns `true`. + fn passed_mark(&self, mut previous: usize, mut current: usize, mark: usize, wrap: usize) -> bool { + // We have three indexes mark (m), previous (p) and current (c) so + // there are fac(3)=6 possibilities how those can be ordered. For both + // cases (passed, !passed), there are three wrapped variations each: + // !passed: 1. m <= p <= c, 2. c < m <= p, 3. p <= c < m + // passed: 1. m <= c < p, 2. p < m <= c, 3. c < p < m + // By enforcing p >= m and c >= m (reverting the wrap), we only have to + // check the first case. + if previous < mark { + previous += wrap; + } + if current < mark { + current += wrap; + } + current < previous && current >= mark + } + + /// Reads and removes the available contents of the dma buffer into `buf`. + /// Returns `Err(Error::Overrun)` if an overrun is detected but there is no + /// guarantee that every overrun can be detected. + /// On success, returns the number of words written to `buf`. + pub fn read(&mut self, buf: &mut [T]) -> Result + where + B: AsRef<[T]>, + T: Copy, + { + // We do our best to figure out when an overrun occurred but without + // risking false positives. + // + // One possibility to detect an overrun is by examining the read- and + // write-indexes: If the write-index passes the read-index, that is an + // overrun condition because an unread value is overwritten. This check + // can fail if the write-index passed the read-index but due to + // wrapping, this can not be detected. For example, this can happen if + // `capacity` many words were written since the last read which looks + // like no word has been written. + // + // Another possibility to detect overruns is by checking the + // TransferComplete and HalfTransferComplete flags. For example, the + // TransferComplete flag is set when the write-index wraps around so + // whenever we encounter this flag the new write-index should be + // smaller than the previous one. If it is not, more than `capacity` + // many words must have been written which definitely must be an + // overrun. Another possibility to formulate this condition is to check + // wheter the write-index passed index 0. A similar condition can be + // formulated for the HalfTransferComplete flag, i.e. check whether the + // write-index passed index capacity/2. + // + // Unfortunately, even both checks together can not guarantee that we + // detect all overruns. + // Example: + // read = 2, write = 3, 2*capacity-2 words written => write = 1. + let capacity = self.buffer.as_ref().len(); + // We read the flags before reading the current write-index because if + // another word is written between those two accesses, this ordering + // prevents a false positive overrun error. + let isr = self.payload.channel.isr(); + let half_complete_flag = isr.$htifX().bit_is_set(); + if half_complete_flag { + self.payload.channel.ifcr().write(|w| w.$chtifX().set_bit()); + } + let transfer_complete_flag = isr.$tcifX().bit_is_set(); + if transfer_complete_flag { + self.payload.channel.ifcr().write(|w| w.$ctcifX().set_bit()); + } + let write_current = capacity - self.payload.channel.get_cndtr() as usize; + // Copy the data before examining the overrun conditions. If the + // overrun happens shortly after the flags and write-index were read, + // we can not detect it anyways. So we can only hope that we have + // already read the word(s) that will be overwritten. + let available = if write_current >= self.read_index { + write_current - self.read_index + } else { + capacity + write_current - self.read_index + }; + let read_len = core::cmp::min(available, buf.len()); + if self.read_index + read_len <= capacity { + // non-wrapping read + buf[..read_len].copy_from_slice(&self.buffer.as_ref()[self.read_index..self.read_index+read_len]); + } else { + // wrapping read + let first_read_len = capacity - self.read_index; + let second_read_len = read_len - first_read_len; + buf[..first_read_len].copy_from_slice(&self.buffer.as_ref()[self.read_index..]); + buf[first_read_len..read_len].copy_from_slice(&self.buffer.as_ref()[..second_read_len]); + } + // For checking the overrun conditions, it is important that we use the + // old read_index so do not increment it yet but check overrun + // conditions first. + // For odd buffer sizes, the half-complete flag is set at + // ceil(capacity/2). + let overrun = + self.passed_mark(self.write_previous, write_current, self.read_index, capacity) + || (transfer_complete_flag && !self.passed_mark(self.write_previous, write_current, 0, capacity)) + || (half_complete_flag && !self.passed_mark(self.write_previous, write_current, (capacity+1)/2, capacity)); + self.write_previous = write_current; + if overrun { + self.read_index = write_current; + Err(Error::Overrun) + } else { + self.read_index += read_len; + if self.read_index >= capacity { + self.read_index -= capacity; + } + Ok(read_len) + } + } + + /// Stops the transfer and returns the underlying buffer and RxDma + pub fn stop(mut self) -> (&'static mut B, RxDma) { + self.payload.stop(); + + (self.buffer, self.payload) + } + } + + impl Transfer> + where + RxDma: TransferPayload, + { + pub fn is_done(&self) -> bool { + !self.payload.channel.in_progress() + } + + pub fn wait(mut self) -> (BUFFER, RxDma) { + // XXX should we check for transfer errors here? + // The manual says "A DMA transfer error can be generated by reading + // from or writing to a reserved address space". I think it's impossible + // to get to that state with our type safe API and *safe* Rust. + while !self.is_done() {} + + self.payload.stop(); + + // TODO can we weaken this compiler barrier? + // NOTE(compiler_fence) operations on `buffer` should not be reordered + // before the previous statement, which marks the DMA transfer as done + atomic::compiler_fence(Ordering::SeqCst); + + // `Transfer` has a `Drop` implementation because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields directly. + self.extract_inner_without_drop() + } + } + + impl Transfer> + where + TxDma: TransferPayload, + { + pub fn is_done(&self) -> bool { + !self.payload.channel.in_progress() + } + + pub fn wait(mut self) -> (BUFFER, TxDma) { + // XXX should we check for transfer errors here? + // The manual says "A DMA transfer error can be generated by reading + // from or writing to a reserved address space". I think it's impossible + // to get to that state with our type safe API and *safe* Rust. + while !self.is_done() {} + + self.payload.stop(); + + // TODO can we weaken this compiler barrier? + // NOTE(compiler_fence) operations on `buffer` should not be reordered + // before the previous statement, which marks the DMA transfer as done + atomic::compiler_fence(Ordering::SeqCst); + + // `Transfer` has a `Drop` implementation because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields directly. + self.extract_inner_without_drop() + } + } + + impl Transfer> + where + RxDma: TransferPayload, + { + pub fn peek(&self) -> &[T] + where + BUFFER: AsRef<[T]>, + { + let pending = self.payload.channel.get_cndtr() as usize; + + let capacity = self.buffer.as_ref().len(); + + &self.buffer.as_ref()[..(capacity - pending)] + } + } + + impl Transfer> + where + TxDma: TransferPayload, + { + pub fn peek(&self) -> &[T] + where + BUFFER: AsRef<[T]> + { + let pending = self.payload.channel.get_cndtr() as usize; + + let capacity = self.buffer.as_ref().len(); + + &self.buffer.as_ref()[..(capacity - pending)] + } + } + )+ + + impl DmaExt for $DMAX { + type Channels = Channels; + + fn split(self, ahb: &mut AHB1) -> Channels { + <$DMAX>::enable(ahb); + + #[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" + ))] + ahb.enr().modify(|_, w| w.dmamux1en().set_bit()); + + // reset the DMA control registers (stops all on-going transfers) + $( + self.$ccrX.reset(); + )+ + + Channels((), $($CX { }),+) + } + } + } + )+ + } +} + +dma! { + DMA1: (dma1, { + C1: ( + ccr1, CCR1, + cndtr1, CNDTR1, + cpar1, CPAR1, + cmar1, CMAR1, + htif1, tcif1, + chtif1, ctcif1, cgif1, + teif1, cteif1 + ), + C2: ( + ccr2, CCR2, + cndtr2, CNDTR2, + cpar2, CPAR2, + cmar2, CMAR2, + htif2, tcif2, + chtif2, ctcif2, cgif2, + teif2, cteif2 + ), + C3: ( + ccr3, CCR3, + cndtr3, CNDTR3, + cpar3, CPAR3, + cmar3, CMAR3, + htif3, tcif3, + chtif3, ctcif3, cgif3, + teif3, cteif3 + ), + C4: ( + ccr4, CCR4, + cndtr4, CNDTR4, + cpar4, CPAR4, + cmar4, CMAR4, + htif4, tcif4, + chtif4, ctcif4, cgif4, + teif4, cteif4 + ), + C5: ( + ccr5, CCR5, + cndtr5, CNDTR5, + cpar5, CPAR5, + cmar5, CMAR5, + htif5, tcif5, + chtif5, ctcif5, cgif5, + teif5, cteif5 + ), + C6: ( + ccr6, CCR6, + cndtr6, CNDTR6, + cpar6, CPAR6, + cmar6, CMAR6, + htif6, tcif6, + chtif6, ctcif6, cgif6, + teif6, cteif6 + ), + C7: ( + ccr7, CCR7, + cndtr7, CNDTR7, + cpar7, CPAR7, + cmar7, CMAR7, + htif7, tcif7, + chtif7, ctcif7, cgif7, + teif7, cteif7 + ), + }), + DMA2: (dma2, { + C1: ( + ccr1, CCR1, + cndtr1, CNDTR1, + cpar1, CPAR1, + cmar1, CMAR1, + htif1, tcif1, + chtif1, ctcif1, cgif1, + teif1, cteif1 + ), + C2: ( + ccr2, CCR2, + cndtr2, CNDTR2, + cpar2, CPAR2, + cmar2, CMAR2, + htif2, tcif2, + chtif2, ctcif2, cgif2, + teif2, cteif2 + ), + C3: ( + ccr3, CCR3, + cndtr3, CNDTR3, + cpar3, CPAR3, + cmar3, CMAR3, + htif3, tcif3, + chtif3, ctcif3, cgif3, + teif3, cteif3 + ), + C4: ( + ccr4, CCR4, + cndtr4, CNDTR4, + cpar4, CPAR4, + cmar4, CMAR4, + htif4, tcif4, + chtif4, ctcif4, cgif4, + teif4, cteif4 + ), + C5: ( + ccr5, CCR5, + cndtr5, CNDTR5, + cpar5, CPAR5, + cmar5, CMAR5, + htif5, tcif5, + chtif5, ctcif5, cgif5, + teif5, cteif5 + ), + C6: ( + ccr6, CCR6, + cndtr6, CNDTR6, + cpar6, CPAR6, + cmar6, CMAR6, + htif6, tcif6, + chtif6, ctcif6, cgif6, + teif6, cteif6 + ), + C7: ( + ccr7, CCR7, + cndtr7, CNDTR7, + cpar7, CPAR7, + cmar7, CMAR7, + htif7, tcif7, + chtif7, ctcif7, cgif7, + teif7, cteif7 + ), + }), +} + +/// DMA Receiver +pub struct RxDma { + pub(crate) payload: PAYLOAD, + pub channel: RXCH, +} + +/// DMA Transmitter +pub struct TxDma { + pub(crate) payload: PAYLOAD, + pub channel: TXCH, +} + +/// DMA Receiver/Transmitter +pub struct RxTxDma { + pub(crate) payload: PAYLOAD, + pub rx_channel: RXCH, + pub tx_channel: TXCH, +} + +pub trait Receive { + type RxChannel; + type TransmittedWord; +} + +pub trait Transmit { + type TxChannel; + type ReceivedWord; +} + +pub trait ReceiveTransmit { + type RxChannel; + type TxChannel; + type TransferedWord; +} + +/// Trait for circular DMA readings from peripheral to memory. +pub trait CircReadDma: Receive +where + &'static mut B: StaticWriteBuffer, + B: 'static, + Self: core::marker::Sized, +{ + fn circ_read(self, buffer: &'static mut B) -> CircBuffer; +} + +/// Trait for DMA readings from peripheral to memory. +pub trait ReadDma: Receive +where + B: StaticWriteBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn read(self, buffer: B) -> Transfer; +} + +/// Trait for DMA writing from memory to peripheral. +pub trait WriteDma: Transmit +where + B: StaticReadBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn write(self, buffer: B) -> Transfer; +} + +/// Trait for DMA simultaneously writing and reading between memory and peripheral. +pub trait TransferDma: ReceiveTransmit +where + B: StaticWriteBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn transfer(self, buffer: B) -> Transfer; +} diff --git a/stm32l4xx-hal/src/dmamux.rs b/stm32l4xx-hal/src/dmamux.rs new file mode 100644 index 0000000..83bed1c --- /dev/null +++ b/stm32l4xx-hal/src/dmamux.rs @@ -0,0 +1,628 @@ +//! Direct Memory Access Multiplexing + +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use core::convert::TryFrom; +use core::convert::TryInto; + +use crate::dma::{dma1, dma2}; + +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +))] +use crate::stm32::DMAMUX1 as DMAMUX; + +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + Invalid, +} + +/// Input DMA request line selected +pub enum DmaInput { + Generator0, + Generator1, + Generator2, + Generator3, + Adc1, + Adc2, + Adc3, + Dac1Ch1, + Dac1Ch2, + Spi1Rx, + Spi1Tx, + Spi2Rx, + Spi2Tx, + Spi3Rx, + Spi3Tx, + I2c1Rx, + I2c1Tx, + I2c2Rx, + I2c2Tx, + I2c3Rx, + I2c3Tx, + I2c4Rx, + I2c4Tx, + Usart1Rx, + Usart1Tx, + Usart2Rx, + Usart2Tx, + Usart3Rx, + Usart3Tx, + Uart4Rx, + Uart4Tx, + Uart5Rx, + Uart5Tx, + LpUart1Rx, + LpUart1Tx, + Sai1A, + Sai1B, + Sai2A, + Sai2B, + QuadSpi, + OctoSpi1, + OctoSpi2, + Tim6Up, + Tim7Up, + Tim1Ch1, + Tim1Ch2, + Tim1Ch3, + Tim1Ch4, + Tim1Up, + Tim1Trig, + Tim1Com, + Tim8Ch1, + Tim8Ch2, + Tim8Ch3, + Tim8Ch4, + Tim8Up, + Tim8Trig, + Tim8Com, + Tim2Ch1, + Tim2Ch2, + Tim2Ch3, + Tim2Ch4, + Tim2Up, + Tim3Ch1, + Tim3Ch2, + Tim3Ch3, + Tim3Ch4, + Tim3Up, + Tim3Trig, + Tim4Ch1, + Tim4Ch2, + Tim4Ch3, + Tim4Ch4, + Tim4Up, + Tim5Ch1, + Tim5Ch2, + Tim5Ch3, + Tim5Ch4, + Tim5Up, + Tim5Trig, + Tim15Ch1, + Tim15Up, + Tim15Trig, + Tim15Com, + Tim16Ch1, + Tim16Up, + Tim17Ch1, + Tim17Up, + Dfsdm1Flt0, + Dfsdm1Flt1, + Dfsdm1Flt2, + Dfsdm1Flt3, + Dcmi, + AesIn, + AesOut, + HashIn, + Swpmi1Rx, + Swpmi1Tx, + SdMmc1, +} + +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +))] +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +enum DMAREQ_ID_A { + NONE = 0, + GENERATOR0 = 1, + GENERATOR1 = 2, + GENERATOR2 = 3, + GENERATOR3 = 4, + ADC1 = 5, + DAC1_CH1 = 6, + DAC1_CH2 = 7, + TIM6_UP = 8, + TIM7_UP = 9, + SPI1_RX = 10, + SPI1_TX = 11, + SPI2_RX = 12, + SPI2_TX = 13, + SPI3_RX = 14, + SPI3_TX = 15, + I2C1_RX = 16, + I2C1_TX = 17, + I2C2_RX = 18, + I2C2_TX = 19, + I2C3_RX = 20, + I2C3_TX = 21, + I2C4_RX = 22, + I2C4_TX = 23, + USART1_RX = 24, + USART1_TX = 25, + USART2_RX = 26, + USART2_TX = 27, + USART3_RX = 28, + USART3_TX = 29, + UART4_RX = 30, + UART4_TX = 31, + UART5_RX = 32, + UART5_TX = 33, + LPUART1_RX = 34, + LPUART1_TX = 35, + SAI1_A = 36, + SAI1_B = 37, + SAI2_A = 38, + SAI2_B = 39, + OCTOSPI1 = 40, + OCTOSPI2 = 41, + TIM1_CH1 = 42, + TIM1_CH2 = 43, + TIM1_CH3 = 44, + TIM1_CH4 = 45, + TIM1_UP = 46, + TIM1_TRIG = 47, + TIM1_COM = 48, + TIM8_CH1 = 49, + TIM8_CH2 = 50, + TIM8_CH3 = 51, + TIM8_CH4 = 52, + TIM8_UP = 53, + TIM8_TRIG = 54, + TIM8_COM = 55, + TIM2_CH1 = 56, + TIM2_CH2 = 57, + TIM2_CH3 = 58, + TIM2_CH4 = 59, + TIM2_UP = 60, + TIM3_CH1 = 61, + TIM3_CH2 = 62, + TIM3_CH3 = 63, + TIM3_CH4 = 64, + TIM3_UP = 65, + TIM3_TRIG = 66, + TIM4_CH1 = 67, + TIM4_CH2 = 68, + TIM4_CH3 = 69, + TIM4_CH4 = 70, + TIM4_UP = 71, + TIM5_CH1 = 72, + TIM5_CH2 = 73, + TIM5_CH3 = 74, + TIM5_CH4 = 75, + TIM5_UP = 76, + TIM5_TRIG = 77, + TIM15_CH1 = 78, + TIM15_UP = 79, + TIM15_TRIG = 80, + TIM15_COM = 81, + TIM16_CH1 = 82, + TIM16_UP = 83, + TIM17_CH1 = 84, + TIM17_UP = 85, + DFSDM1_FLT0 = 86, + DFSDM1_FLT1 = 87, + DFSDM1_FLT2 = 88, + DFSDM1_FLT3 = 89, + DCMI = 90, + AES_IN = 91, + AES_OUT = 92, + HASH_IN = 93, +} +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +))] +impl From for u8 { + #[inline(always)] + fn from(variant: DMAREQ_ID_A) -> Self { + variant as _ + } +} +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +))] +impl TryFrom for DMAREQ_ID_A { + type Error = Error; + + #[inline(always)] + fn try_from(variant: DmaInput) -> Result { + let result = match variant { + DmaInput::Generator0 => Self::GENERATOR0, + DmaInput::Generator1 => Self::GENERATOR1, + DmaInput::Generator2 => Self::GENERATOR2, + DmaInput::Generator3 => Self::GENERATOR3, + DmaInput::Adc1 => Self::ADC1, + DmaInput::Dac1Ch1 => Self::DAC1_CH1, + DmaInput::Dac1Ch2 => Self::DAC1_CH2, + DmaInput::Tim6Up => Self::TIM6_UP, + DmaInput::Tim7Up => Self::TIM7_UP, + DmaInput::Spi1Rx => Self::SPI1_RX, + DmaInput::Spi1Tx => Self::SPI1_TX, + DmaInput::Spi2Rx => Self::SPI2_RX, + DmaInput::Spi2Tx => Self::SPI2_TX, + DmaInput::Spi3Rx => Self::SPI3_RX, + DmaInput::Spi3Tx => Self::SPI3_TX, + DmaInput::I2c1Rx => Self::I2C1_RX, + DmaInput::I2c1Tx => Self::I2C1_TX, + DmaInput::I2c2Rx => Self::I2C2_RX, + DmaInput::I2c2Tx => Self::I2C2_TX, + DmaInput::I2c3Rx => Self::I2C3_RX, + DmaInput::I2c3Tx => Self::I2C3_TX, + DmaInput::I2c4Rx => Self::I2C4_RX, + DmaInput::I2c4Tx => Self::I2C4_TX, + DmaInput::Usart1Rx => Self::USART1_RX, + DmaInput::Usart1Tx => Self::USART1_TX, + DmaInput::Usart2Rx => Self::USART2_RX, + DmaInput::Usart2Tx => Self::USART2_TX, + DmaInput::Usart3Rx => Self::USART3_RX, + DmaInput::Usart3Tx => Self::USART3_TX, + DmaInput::Uart4Rx => Self::UART4_RX, + DmaInput::Uart4Tx => Self::UART4_TX, + DmaInput::Uart5Rx => Self::UART5_RX, + DmaInput::Uart5Tx => Self::UART5_TX, + DmaInput::LpUart1Rx => Self::LPUART1_RX, + DmaInput::LpUart1Tx => Self::LPUART1_TX, + DmaInput::Sai1A => Self::SAI1_A, + DmaInput::Sai1B => Self::SAI1_B, + DmaInput::Sai2A => Self::SAI2_A, + DmaInput::Sai2B => Self::SAI2_B, + DmaInput::OctoSpi1 => Self::OCTOSPI1, + DmaInput::OctoSpi2 => Self::OCTOSPI2, + DmaInput::Tim1Ch1 => Self::TIM1_CH1, + DmaInput::Tim1Ch2 => Self::TIM1_CH2, + DmaInput::Tim1Ch3 => Self::TIM1_CH3, + DmaInput::Tim1Ch4 => Self::TIM1_CH4, + DmaInput::Tim1Up => Self::TIM1_UP, + DmaInput::Tim1Trig => Self::TIM1_TRIG, + DmaInput::Tim1Com => Self::TIM1_COM, + DmaInput::Tim8Ch1 => Self::TIM8_CH1, + DmaInput::Tim8Ch2 => Self::TIM8_CH2, + DmaInput::Tim8Ch3 => Self::TIM8_CH3, + DmaInput::Tim8Ch4 => Self::TIM8_CH4, + DmaInput::Tim8Up => Self::TIM8_UP, + DmaInput::Tim8Trig => Self::TIM8_TRIG, + DmaInput::Tim8Com => Self::TIM8_COM, + DmaInput::Tim2Ch1 => Self::TIM2_CH1, + DmaInput::Tim2Ch2 => Self::TIM2_CH2, + DmaInput::Tim2Ch3 => Self::TIM2_CH3, + DmaInput::Tim2Ch4 => Self::TIM2_CH4, + DmaInput::Tim2Up => Self::TIM2_UP, + DmaInput::Tim3Ch1 => Self::TIM3_CH1, + DmaInput::Tim3Ch2 => Self::TIM3_CH2, + DmaInput::Tim3Ch3 => Self::TIM3_CH3, + DmaInput::Tim3Ch4 => Self::TIM3_CH4, + DmaInput::Tim3Up => Self::TIM3_UP, + DmaInput::Tim3Trig => Self::TIM3_TRIG, + DmaInput::Tim4Ch1 => Self::TIM4_CH1, + DmaInput::Tim4Ch2 => Self::TIM4_CH2, + DmaInput::Tim4Ch3 => Self::TIM4_CH3, + DmaInput::Tim4Ch4 => Self::TIM4_CH4, + DmaInput::Tim4Up => Self::TIM4_UP, + DmaInput::Tim5Ch1 => Self::TIM5_CH1, + DmaInput::Tim5Ch2 => Self::TIM5_CH2, + DmaInput::Tim5Ch3 => Self::TIM5_CH3, + DmaInput::Tim5Ch4 => Self::TIM5_CH4, + DmaInput::Tim5Up => Self::TIM5_UP, + DmaInput::Tim5Trig => Self::TIM5_TRIG, + DmaInput::Tim15Ch1 => Self::TIM15_CH1, + DmaInput::Tim15Up => Self::TIM15_UP, + DmaInput::Tim15Trig => Self::TIM15_TRIG, + DmaInput::Tim15Com => Self::TIM15_COM, + DmaInput::Tim16Ch1 => Self::TIM16_CH1, + DmaInput::Tim16Up => Self::TIM16_UP, + DmaInput::Tim17Ch1 => Self::TIM17_CH1, + DmaInput::Tim17Up => Self::TIM17_UP, + DmaInput::Dfsdm1Flt0 => Self::DFSDM1_FLT0, + DmaInput::Dfsdm1Flt1 => Self::DFSDM1_FLT1, + DmaInput::Dfsdm1Flt2 => Self::DFSDM1_FLT2, + DmaInput::Dfsdm1Flt3 => Self::DFSDM1_FLT3, + DmaInput::Dcmi => Self::DCMI, + DmaInput::AesIn => Self::AES_IN, + DmaInput::AesOut => Self::AES_OUT, + DmaInput::HashIn => Self::HASH_IN, + _ => return Err(Error::Invalid), + }; + + Ok(result) + } +} + +#[cfg(not(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +)))] +macro_rules! cselr { + ($($DMAX_CY_SEL:ident: { + $( ($field:ident, $bits:literal, [ $( $input:path ),+ ]), )+ + },)+) => { + $( + #[derive(Clone, Copy, Debug, PartialEq)] + #[repr(u8)] + enum $DMAX_CY_SEL { + $( + $field = $bits, + )+ + } + impl From<$DMAX_CY_SEL> for u8 { + #[inline(always)] + fn from(variant: $DMAX_CY_SEL) -> Self { + variant as _ + } + } + impl TryFrom for $DMAX_CY_SEL { + type Error = Error; + + #[inline(always)] + fn try_from(variant: DmaInput) -> Result { + match variant { + $( + $( + $input => Ok(Self::$field), + )+ + )+ + _ => Err(Error::Invalid), + } + } + } + )+ + }; +} + +#[cfg(not(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +)))] +cselr! { + DMA1_C1_SEL: { + (ADC1, 0b0000, [DmaInput::Adc1]), + (TIM2_CH3, 0b0100, [DmaInput::Tim2Ch3]), + (TIM17_CH1_TIM17_UP, 0b0101, [DmaInput::Tim17Ch1, DmaInput::Tim17Up]), + (TIM4_CH1, 0b0110, [DmaInput::Tim4Ch1]), + }, + DMA1_C2_SEL: { + (ADC2, 0b0000, [DmaInput::Adc2]), + (SPI1_RX, 0b0001, [DmaInput::Spi1Rx]), + (USART3_TX, 0b0010, [DmaInput::Usart3Tx]), + (I2C3_TX, 0b0011, [DmaInput::I2c3Tx]), + (TIM2_UP, 0b0100, [DmaInput::Tim2Up]), + (TIM3_CH3, 0b0101, [DmaInput::Tim3Ch3]), + (TIM1_CH1, 0b0111, [DmaInput::Tim1Ch1]), + }, + DMA1_C3_SEL: { + (ADC3, 0b0000, [DmaInput::Adc3]), + (SPI1_TX, 0b0001, [DmaInput::Spi1Tx]), + (USART3_RX, 0b0010, [DmaInput::Usart3Rx]), + (I2C3_RX, 0b0011, [DmaInput::I2c3Rx]), + (TIM16_CH1_TIM16_UP, 0b0100, [DmaInput::Tim16Ch1, DmaInput::Tim16Up]), + (TIM3_CH4_TIM3_UP, 0b0101, [DmaInput::Tim3Ch4, DmaInput::Tim3Up]), + (TIM6_UP_DAC_CH1, 0b0110, [DmaInput::Tim6Up, DmaInput::Dac1Ch1]), + (TIM1_CH2, 0b0111, [DmaInput::Tim1Ch2]), + }, + DMA1_C4_SEL: { + (DFSDM1_FLT0, 0b0000, [DmaInput::Dfsdm1Flt0]), + (SPI2_RX, 0b0001, [DmaInput::Spi2Rx]), + (USART1_TX, 0b0010, [DmaInput::Usart1Tx]), + (I2C2_TX, 0b0011, [DmaInput::I2c2Tx]), + (TIM7_UP_DAC_CH2, 0b0101, [DmaInput::Tim7Up, DmaInput::Dac1Ch2]), + (TIM4_CH2, 0b0110, [DmaInput::Tim4Ch2]), + (TIM1_CH4_TIM1_TRIG_TIM1_COM, 0b0111, [DmaInput::Tim1Ch4, DmaInput::Tim1Trig, DmaInput::Tim1Com]), + }, + DMA1_C5_SEL: { + (DFSDM1_FLT1, 0b0000, [DmaInput::Dfsdm1Flt1]), + (SPI2_TX, 0b0001, [DmaInput::Spi2Tx]), + (USART1_RX, 0b0010, [DmaInput::Usart1Rx]), + (I2C2_RX, 0b0011, [DmaInput::I2c2Rx]), + (TIM2_CH1, 0b0100, [DmaInput::Tim2Ch1]), + (QUADSPI, 0b0101, [DmaInput::QuadSpi]), + (TIM4_CH3, 0b0110, [DmaInput::Tim4Ch3]), + (TIM15_CH1_TIM15_UP_TIM15_TRIG_TIM15_COM, 0b0111, [DmaInput::Tim15Ch1, DmaInput::Tim15Up, DmaInput::Tim15Trig, DmaInput::Tim15Com]), + }, + DMA1_C6_SEL: { + (DFSDM1_FLT2, 0b0000, [DmaInput::Dfsdm1Flt2]), + (SAI2_A, 0b0001, [DmaInput::Sai2A]), + (USART2_RX, 0b0010, [DmaInput::Usart2Rx]), + (I2C1_TX, 0b0011, [DmaInput::I2c1Tx]), + (TIM16_CH1_TIM16_UP, 0b0100, [DmaInput::Tim16Ch1, DmaInput::Tim16Up]), + (TIM3_CH1_TIM3_TRIG, 0b0101, [DmaInput::Tim3Ch1, DmaInput::Tim3Trig]), + (TIM1_UP, 0b0111, [DmaInput::Tim1Up]), + }, + DMA1_C7_SEL: { + (DFSDM1_FLT3, 0b0000, [DmaInput::Dfsdm1Flt3]), + (SAI2_B, 0b0001, [DmaInput::Sai2B]), + (USART2_TX, 0b0010, [DmaInput::Usart2Tx]), + (I2C1_RX, 0b0011, [DmaInput::I2c1Rx]), + (TIM2_CH2_TIM2_CH4, 0b0100, [DmaInput::Tim2Ch2, DmaInput::Tim2Ch4]), + (TIM17_CH1_TIM17_UP, 0b0101, [DmaInput::Tim17Ch1, DmaInput::Tim17Up]), + (TIM4_UP, 0b0110, [DmaInput::Tim4Up]), + (TIM1_CH3, 0b0111, [DmaInput::Tim1Ch3]), + }, + DMA2_C1_SEL: { + (I2C4_RX, 0b0000, [DmaInput::I2c4Rx]), + (SAI1_A, 0b0001, [DmaInput::Sai1A]), + (UART5_TX, 0b0010, [DmaInput::Uart5Tx]), + (SPI3_RX, 0b0011, [DmaInput::Spi3Rx]), + (SWPMI1_RX, 0b0100, [DmaInput::Swpmi1Rx]), + (TIM5_CH4_TIM5_TRIG, 0b0101, [DmaInput::Tim5Ch4, DmaInput::Tim5Trig]), + (AES_IN, 0b0110, [DmaInput::AesIn]), + (TIM8_CH3_TIM8_UP, 0b0111, [DmaInput::Tim8Ch3, DmaInput::Tim8Up]), + }, + DMA2_C2_SEL: { + (I2C4_TX, 0b0000, [DmaInput::I2c4Tx]), + (SAI1_B, 0b0001, [DmaInput::Sai1B]), + (UART5_RX, 0b0010, [DmaInput::Uart5Rx]), + (SPI3_TX, 0b0011, [DmaInput::Spi3Tx]), + (SWPMI1_TX, 0b0100, [DmaInput::Swpmi1Tx]), + (TIM5_CH3_TIM5_UP, 0b0101, [DmaInput::Tim5Ch3, DmaInput::Tim5Up]), + (AES_OUT, 0b0110, [DmaInput::AesOut]), + (TIM8_CH4_TIM8_TRIG_TIM8_COM, 0b0111, [DmaInput::Tim8Ch4, DmaInput::Tim8Trig, DmaInput::Tim8Com]), + }, + DMA2_C3_SEL: { + (ADC1, 0b0000, [DmaInput::Adc1]), + (SAI2_A, 0b0001, [DmaInput::Sai2A]), + (UART4_TX, 0b0010, [DmaInput::Uart4Tx]), + (SPI1_RX, 0b0100, [DmaInput::Spi1Rx]), + (AES_OUT, 0b0110, [DmaInput::AesOut]), + }, + DMA2_C4_SEL: { + (ADC2, 0b0000, [DmaInput::Adc2]), + (SAI2_B, 0b0001, [DmaInput::Sai2B]), + (TIM6_UP_DAC_CH1, 0b0011, [DmaInput::Tim6Up, DmaInput::Dac1Ch1]), + (SPI1_TX, 0b0100, [DmaInput::Spi1Tx]), + (TIM5_CH2, 0b0101, [DmaInput::Tim5Ch2]), + (SDMMC1, 0b0111, [DmaInput::SdMmc1]), + }, + DMA2_C5_SEL: { + (ADC3, 0b0000, [DmaInput::Adc3]), + (UART4_RX, 0b0010, [DmaInput::Uart4Rx]), + (TIM7_UP_DAC_CH2, 0b0011, [DmaInput::Tim7Up, DmaInput::Dac1Ch2]), + (DCMI, 0b0100, [DmaInput::Dcmi]), + (TIM5_CH1, 0b0101, [DmaInput::Tim5Ch1]), + (AES_IN, 0b0110, [DmaInput::AesIn]), + (SDMMC1, 0b0111, [DmaInput::SdMmc1]), + }, + DMA2_C6_SEL: { + (DCMI, 0b0000, [DmaInput::Dcmi]), + (SAI1_A, 0b0001, [DmaInput::Sai1A]), + (USART1_TX, 0b0010, [DmaInput::Usart1Tx]), + (LPUART1_TX, 0b0100, [DmaInput::LpUart1Tx]), + (I2C1_RX, 0b0101, [DmaInput::I2c1Rx]), + (TIM8_CH1, 0b0111, [DmaInput::Tim8Ch1]), + }, + DMA2_C7_SEL: { + (SAI1_B, 0b0001, [DmaInput::Sai1B]), + (USART1_RX, 0b0010, [DmaInput::Usart1Rx]), + (QUADSPI, 0b0011, [DmaInput::QuadSpi]), + (LPUART1_RX, 0b0100, [DmaInput::LpUart1Rx]), + (I2C1_TX, 0b0101, [DmaInput::I2c1Tx]), + (HASH_IN, 0b0110, [DmaInput::HashIn]), + (TIM8_CH2, 0b0111, [DmaInput::Tim8Ch2]), + }, +} + +pub trait DmaMux { + fn set_request_line(&mut self, request_line: DmaInput) -> Result<(), Error>; +} + +macro_rules! dmamux { + ($($dmaX:ident: { $( $CY:ident: ($cYcr:ident, $cYs:ident, $DMAX_CY_SEL:ident), )+ },)+) => { + $( + $( + impl DmaMux for $dmaX::$CY { + #[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" + ))] + #[inline(always)] + fn set_request_line(&mut self, request_line: DmaInput) -> Result<(), Error> { + let dmareq_id_a: DMAREQ_ID_A = request_line.try_into()?; + let mux = unsafe { &(*DMAMUX::ptr()) }; + unsafe { + mux.$cYcr.modify(|_, w| w.dmareq_id().bits(dmareq_id_a.into())); + } + + Ok(()) + } + + #[cfg(not(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" + )))] + #[inline(always)] + fn set_request_line(&mut self, request_line: DmaInput) -> Result<(), Error> { + let csel_val: $DMAX_CY_SEL = request_line.try_into()?; + self.cselr().modify(|_, w| w.$cYs().bits(csel_val.into())); + + Ok(()) + } + } + )+ + )+ + }; +} + +dmamux! { + dma1: { + C1: (c0cr, c1s, DMA1_C1_SEL), + C2: (c1cr, c2s, DMA1_C2_SEL), + C3: (c2cr, c3s, DMA1_C3_SEL), + C4: (c3cr, c4s, DMA1_C4_SEL), + C5: (c4cr, c5s, DMA1_C5_SEL), + C6: (c5cr, c6s, DMA1_C6_SEL), + C7: (c6cr, c7s, DMA1_C7_SEL), + }, + dma2: { + C1: (c7cr, c1s, DMA2_C1_SEL), + C2: (c8cr, c2s, DMA2_C2_SEL), + C3: (c9cr, c3s, DMA2_C3_SEL), + C4: (c10cr, c4s, DMA2_C4_SEL), + C5: (c11cr, c5s, DMA2_C5_SEL), + C6: (c12cr, c6s, DMA2_C6_SEL), + C7: (c13cr, c7s, DMA2_C7_SEL), + }, +} diff --git a/stm32l4xx-hal/src/flash.rs b/stm32l4xx-hal/src/flash.rs new file mode 100644 index 0000000..5694cf2 --- /dev/null +++ b/stm32l4xx-hal/src/flash.rs @@ -0,0 +1,345 @@ +//! Flash memory module +//! +//! Example usage of flash programming interface: +//! +//! ``` +//! fn program_region(mut flash: flash::Parts) -> Result<(), flash::Error> { +//! // Unlock the flashing module +//! let mut prog = flash.keyr.unlock_flash(&mut flash.sr, &mut flash.cr)?; +//! +//! let page = flash::FlashPage(5); +//! +//! // Perform the erase and programing operation +//! prog.erase_page(page)?; +//! let data = [ +//! 0x1111_1112_1113_1114, +//! 0x2221_2222_2223_2224, +//! 0x3331_3332_3333_3334, +//! ]; +//! prog.write_native(page.to_address(), &data)?; +//! +//! // Check result (not needed, but done for this example) +//! let addr = page.to_address() as *const u64; +//! assert!(unsafe { core::ptr::read(addr) } == data[0]); +//! assert!(unsafe { core::ptr::read(addr.offset(1)) } == data[1]); +//! assert!(unsafe { core::ptr::read(addr.offset(2)) } == data[2]); +//! +//! Ok(()) +//! } +//! ``` + +#![deny(missing_docs)] + +use crate::stm32::{flash, FLASH}; +use crate::traits::flash as flash_trait; +use core::convert::TryInto; +use core::{mem, ops::Drop, ptr}; +pub use flash_trait::{Error, FlashPage, Read, WriteErase}; + +/// Extension trait to constrain the FLASH peripheral +pub trait FlashExt { + /// Constrains the FLASH peripheral to play nicely with the other abstractions + fn constrain(self) -> Parts; +} + +impl FlashExt for FLASH { + fn constrain(self) -> Parts { + Parts { + acr: ACR {}, + pdkeyr: PDKEYR {}, + keyr: KEYR {}, + optkeyr: OPTKEYR {}, + sr: SR {}, + cr: CR {}, + eccr: ECCR {}, + pcrop1sr: PCROP1SR {}, + pcrop1er: PCROP1ER {}, + wrp1ar: WRP1AR {}, + wrp1br: WRP1BR {}, + } + } +} + +/// Constrained FLASH peripheral +pub struct Parts { + /// Opaque ACR register + pub acr: ACR, + /// Opaque PDKEYR register + pub pdkeyr: PDKEYR, + /// Opaque KEYR register + pub keyr: KEYR, + /// Opaque OPTKEYR register + pub optkeyr: OPTKEYR, + /// Opaque SR register + pub sr: SR, + /// Opaque SR register + pub cr: CR, + /// Opaque ECCR register + pub eccr: ECCR, + /// Opaque PCROP1SR register + pub pcrop1sr: PCROP1SR, + /// Opaque PCROP1ER register + pub pcrop1er: PCROP1ER, + /// Opaque WRP1AR register + pub wrp1ar: WRP1AR, + /// Opaque WRP1BR register + pub wrp1br: WRP1BR, +} + +macro_rules! generate_register { + ($a:ident, $b:ident, $name:expr) => { + #[doc = "Opaque "] + #[doc = $name] + #[doc = " register"] + pub struct $a; + + impl $a { + #[allow(unused)] + pub(crate) fn $b(&mut self) -> &flash::$a { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*FLASH::ptr()).$b } + } + } + }; + + ($a:ident, $b:ident) => { + generate_register!($a, $b, stringify!($a)); + }; +} + +generate_register!(ACR, acr); +generate_register!(PDKEYR, pdkeyr); +generate_register!(KEYR, keyr); +generate_register!(OPTKEYR, optkeyr); +generate_register!(SR, sr); +generate_register!(CR, cr); +generate_register!(ECCR, eccr); +generate_register!(PCROP1SR, pcrop1sr); +generate_register!(PCROP1ER, pcrop1er); +generate_register!(WRP1AR, wrp1ar); +generate_register!(WRP1BR, wrp1br); + +const FLASH_KEY1: u32 = 0x4567_0123; +const FLASH_KEY2: u32 = 0xCDEF_89AB; + +impl KEYR { + /// Unlock the flash registers via KEYR to access the flash programming + pub fn unlock_flash<'a>( + &'a mut self, + sr: &'a mut SR, + cr: &'a mut CR, + ) -> Result, Error> { + let keyr = self.keyr(); + unsafe { + keyr.write(|w| w.bits(FLASH_KEY1)); + keyr.write(|w| w.bits(FLASH_KEY2)); + } + + if cr.cr().read().lock().bit_is_clear() { + Ok(FlashProgramming { sr, cr }) + } else { + Err(Error::Failure) + } + } +} + +impl FlashPage { + /// This gives the starting address of a flash page in physical address + pub const fn to_address(&self) -> usize { + 0x0800_0000 + self.0 * 2048 + } +} + +/// Flash programming interface +pub struct FlashProgramming<'a> { + sr: &'a mut SR, + cr: &'a mut CR, +} + +impl<'a> Drop for FlashProgramming<'a> { + fn drop(&mut self) { + // Lock on drop + self.lock(); + } +} + +impl<'a> Read for FlashProgramming<'a> { + type NativeType = u8; + + #[inline] + fn read_native(&self, address: usize, array: &mut [Self::NativeType]) { + let mut address = address as *const Self::NativeType; + + for data in array { + unsafe { + *data = ptr::read(address); + address = address.add(1); + } + } + } + + #[inline] + fn read(&self, address: usize, buf: &mut [u8]) { + self.read_native(address, buf); + } +} + +impl<'a> WriteErase for FlashProgramming<'a> { + type NativeType = u64; + + fn status(&self) -> flash_trait::Result { + let sr = unsafe { &(*FLASH::ptr()).sr }.read(); + + if sr.bsy().bit_is_set() { + Err(flash_trait::Error::Busy) + } else if sr.pgaerr().bit_is_set() || sr.progerr().bit_is_set() || sr.wrperr().bit_is_set() + { + Err(flash_trait::Error::Illegal) + } else { + Ok(()) + } + } + + fn erase_page(&mut self, page: flash_trait::FlashPage) -> flash_trait::Result { + match page.0 { + 0..=255 => { + self.cr.cr().modify(|_, w| unsafe { + w.bker() + .clear_bit() + .pnb() + .bits(page.0 as u8) + .per() + .set_bit() + }); + } + 256..=511 => { + self.cr.cr().modify(|_, w| unsafe { + w.bker() + .set_bit() + .pnb() + .bits((page.0 - 256) as u8) + .per() + .set_bit() + }); + } + _ => { + return Err(flash_trait::Error::PageOutOfRange); + } + } + + self.cr.cr().modify(|_, w| w.start().set_bit()); + + let res = self.wait(); + + self.cr.cr().modify(|_, w| w.per().clear_bit()); + + res + } + + fn write_native(&mut self, address: usize, array: &[Self::NativeType]) -> flash_trait::Result { + // NB: The check for alignment of the address, and that the flash is erased is made by the + // flash controller. The `wait` function will return the proper error codes. + let mut address = address as *mut u32; + + self.cr.cr().modify(|_, w| w.pg().set_bit()); + + for dword in array { + unsafe { + ptr::write_volatile(address, *dword as u32); + ptr::write_volatile(address.add(1), (*dword >> 32) as u32); + + address = address.add(2); + } + + self.wait()?; + + if self.sr.sr().read().eop().bit_is_set() { + self.sr.sr().modify(|_, w| w.eop().clear_bit()); + } + } + + self.cr.cr().modify(|_, w| w.pg().clear_bit()); + + Ok(()) + } + + fn write(&mut self, address: usize, data: &[u8]) -> flash_trait::Result { + let address_offset = address % mem::align_of::(); + let unaligned_size = (mem::size_of::() - address_offset) + % mem::size_of::(); + + if unaligned_size > 0 { + let unaligned_data = &data[..unaligned_size]; + // Handle unaligned address data, make it into a native write + let mut data = 0xffff_ffff_ffff_ffffu64; + for b in unaligned_data { + data = (data >> 8) | ((*b as Self::NativeType) << 56); + } + + let unaligned_address = address - address_offset; + let native = &[data]; + self.write_native(unaligned_address, native)?; + } + + // Handle aligned address data + let aligned_data = &data[unaligned_size..]; + let mut aligned_address = if unaligned_size > 0 { + address - address_offset + mem::size_of::() + } else { + address + }; + + let mut chunks = aligned_data.chunks_exact(mem::size_of::()); + + while let Some(exact_chunk) = chunks.next() { + // Write chunks + let native = &[Self::NativeType::from_ne_bytes( + exact_chunk.try_into().unwrap(), + )]; + self.write_native(aligned_address, native)?; + aligned_address += mem::size_of::(); + } + + let rem = chunks.remainder(); + + if !rem.is_empty() { + let mut data = 0xffff_ffff_ffff_ffffu64; + // Write remainder + for b in rem.iter().rev() { + data = (data << 8) | *b as Self::NativeType; + } + + let native = &[data]; + self.write_native(aligned_address, native)?; + } + + Ok(()) + } +} + +impl<'a> FlashProgramming<'a> { + /// Lock the flash memory controller + fn lock(&mut self) { + self.cr.cr().modify(|_, w| w.lock().set_bit()); + } + + /// Wait till last flash operation is complete + fn wait(&mut self) -> flash_trait::Result { + while self.sr.sr().read().bsy().bit_is_set() {} + + self.status() + } + + /// Erase all flash pages, note that this will erase the current running program if it is not + /// called from a program running in RAM. + pub fn erase_all_pages(&mut self) -> flash_trait::Result { + self.cr.cr().modify(|_, w| w.mer1().set_bit()); + self.cr.cr().modify(|_, w| w.start().set_bit()); + + let res = self.wait(); + + self.cr.cr().modify(|_, w| w.mer1().clear_bit()); + + res + } +} diff --git a/stm32l4xx-hal/src/gpio.rs b/stm32l4xx-hal/src/gpio.rs new file mode 100644 index 0000000..88bf9db --- /dev/null +++ b/stm32l4xx-hal/src/gpio.rs @@ -0,0 +1,826 @@ +//! General Purpose Input / Output + +pub use crate::hal::digital::v2::PinState; +use crate::hal::digital::v2::{InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin}; +use core::convert::Infallible; +use core::marker::PhantomData; + +use crate::pac::{self, EXTI, SYSCFG}; +use crate::rcc::{Enable, AHB2, APB2}; + +mod convert; + +mod partially_erased; +pub use partially_erased::{PEPin, PartiallyErasedPin}; +mod erased; +pub use erased::{EPin, ErasedPin}; + +/// Extension trait to split a GPIO peripheral in independent pins and registers +pub trait GpioExt { + /// The to split the GPIO into + type Parts; + + /// Splits the GPIO block into independent pins and registers + fn split(self, ahb: &mut AHB2) -> Self::Parts; +} + +/// Input mode (type state) +pub struct Input { + _mode: PhantomData, +} + +/// Floating input (type state) +pub struct Floating; +/// Pulled down input (type state) +pub struct PullDown; +/// Pulled up input (type state) +pub struct PullUp; + +/// Output mode (type state) +pub struct Output { + _mode: PhantomData, +} + +/// Push pull output (type state) +pub struct PushPull; +/// Open drain output (type state) +pub struct OpenDrain; + +/// Analog mode (type state) +pub struct Analog; + +pub type Debugger = Alternate; + +/// GPIO Pin speed selection +pub enum Speed { + Low = 0, + Medium = 1, + High = 2, + VeryHigh = 3, +} + +pub trait PinExt { + type Mode; + /// Return pin number + fn pin_id(&self) -> u8; + /// Return port number + fn port_id(&self) -> u8; +} + +/// Alternate mode (type state) +pub struct Alternate { + _mode: PhantomData, +} + +#[derive(Debug, PartialEq)] +pub enum Edge { + Rising, + Falling, + RisingFalling, +} + +mod sealed { + /// Marker trait that show if `ExtiPin` can be implemented + pub trait Interruptable {} +} + +use sealed::Interruptable; +impl Interruptable for Output {} +impl Interruptable for Input {} + +/// External Interrupt Pin +pub trait ExtiPin { + fn make_interrupt_source(&mut self, syscfg: &mut SYSCFG, apb2: &mut APB2); + fn trigger_on_edge(&mut self, exti: &mut EXTI, level: Edge); + fn enable_interrupt(&mut self, exti: &mut EXTI); + fn disable_interrupt(&mut self, exti: &mut EXTI); + fn clear_interrupt_pending_bit(&mut self); + fn check_interrupt(&self) -> bool; +} + +impl ExtiPin for PIN +where + PIN: PinExt, + PIN::Mode: Interruptable, +{ + /// Make corresponding EXTI line sensitive to this pin + #[inline(always)] + fn make_interrupt_source(&mut self, syscfg: &mut SYSCFG, apb2: &mut APB2) { + // SYSCFG clock must be enabled in order to do register writes + SYSCFG::enable(apb2); + + let i = self.pin_id(); + let port = self.port_id() as u32; + let offset = 4 * (i % 4); + match i { + 0..=3 => { + syscfg.exticr1.modify(|r, w| unsafe { + w.bits((r.bits() & !(0xf << offset)) | (port << offset)) + }); + } + 4..=7 => { + syscfg.exticr2.modify(|r, w| unsafe { + w.bits((r.bits() & !(0xf << offset)) | (port << offset)) + }); + } + 8..=11 => { + syscfg.exticr3.modify(|r, w| unsafe { + w.bits((r.bits() & !(0xf << offset)) | (port << offset)) + }); + } + 12..=15 => { + syscfg.exticr4.modify(|r, w| unsafe { + w.bits((r.bits() & !(0xf << offset)) | (port << offset)) + }); + } + _ => unreachable!(), + } + } + + /// Generate interrupt on rising edge, falling edge or both + #[inline(always)] + fn trigger_on_edge(&mut self, exti: &mut EXTI, edge: Edge) { + let i = self.pin_id(); + match edge { + Edge::Rising => { + exti.rtsr1 + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << i)) }); + exti.ftsr1 + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << i)) }); + } + Edge::Falling => { + exti.ftsr1 + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << i)) }); + exti.rtsr1 + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << i)) }); + } + Edge::RisingFalling => { + exti.rtsr1 + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << i)) }); + exti.ftsr1 + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << i)) }); + } + } + } + + /// Enable external interrupts from this pin. + #[inline(always)] + fn enable_interrupt(&mut self, exti: &mut EXTI) { + exti.imr1 + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << self.pin_id())) }); + } + + /// Disable external interrupts from this pin + #[inline(always)] + fn disable_interrupt(&mut self, exti: &mut EXTI) { + exti.imr1 + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.pin_id())) }); + } + + /// Clear the interrupt pending bit for this pin + #[inline(always)] + fn clear_interrupt_pending_bit(&mut self) { + unsafe { (*EXTI::ptr()).pr1.write(|w| w.bits(1 << self.pin_id())) }; + } + + /// Reads the interrupt pending bit for this pin + #[inline(always)] + fn check_interrupt(&self) -> bool { + unsafe { ((*EXTI::ptr()).pr1.read().bits() & (1 << self.pin_id())) != 0 } + } +} + +/// Opaque MODER register +pub struct MODER { + _0: (), +} + +impl MODER

{ + pub(crate) fn new() -> Self { + Self { _0: () } + } +} + +/// Opaque OTYPER register +pub struct OTYPER { + _0: (), +} + +impl OTYPER

{ + pub(crate) fn new() -> Self { + Self { _0: () } + } +} + +/// Opaque OSPEEDR register +pub struct OSPEEDR { + _0: (), +} +impl OSPEEDR

{ + pub(crate) fn new() -> Self { + Self { _0: () } + } +} + +/// Opaque PUPDR register +pub struct PUPDR { + _0: (), +} + +impl PUPDR

{ + pub(crate) fn new() -> Self { + Self { _0: () } + } +} + +macro_rules! gpio { + ($GPIOX:ident, $gpiox:ident, $PXx:ident, $port_id:literal, $extigpionr:expr, $({ $pwrenable:expr },)? [ + $($PXi:ident: ($pxi:ident, $i:expr, $MODE:ty, $HL:ident, $exticri:ident),)+ + ]) => { + /// GPIO + pub mod $gpiox { + use crate::stm32::$GPIOX; + + use crate::rcc::{AHB2, Enable, Reset}; + use super::{Afr, Analog, GpioExt, Pin, H8, L8, MODER, OTYPER, OSPEEDR, PUPDR}; + + /// GPIO parts + pub struct Parts { + /// Opaque AFRH register + pub afrh: Afr, + /// Opaque AFRL register + pub afrl: Afr, + /// Opaque MODER register + pub moder: MODER<$port_id>, + /// Opaque OTYPER register + pub otyper: OTYPER<$port_id>, + /// Opaque OSPEEDR register + pub ospeedr: OSPEEDR<$port_id>, + /// Opaque PUPDR register + pub pupdr: PUPDR<$port_id>, + $( + /// Pin + pub $pxi: $PXi<$MODE>, + )+ + } + + $( + pub type $PXi = Pin; + )+ + + impl GpioExt for $GPIOX { + type Parts = Parts; + + fn split(self, ahb: &mut AHB2) -> Parts { + <$GPIOX>::enable(ahb); + <$GPIOX>::reset(ahb); + $($pwrenable)? + + Parts { + afrh: Afr::new(), + afrl: Afr::new(), + moder: MODER::new(), + otyper: OTYPER::new(), + ospeedr: OSPEEDR::new(), + pupdr: PUPDR::new(), + $( + $pxi: $PXi::new(), + )+ + } + } + } + } + + pub use $gpiox::{ + $($PXi,)* + }; + } +} + +/// Generic pin type +/// +/// - `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section). +/// - `HL` represents high 8 or low 8 pin. +/// - `P` is port name: `A` for GPIOA, `B` for GPIOB, etc. +/// - `N` is pin number: from `0` to `15`. +pub struct Pin { + _mode: PhantomData<(MODE, HL)>, +} + +impl Pin { + const fn new() -> Self { + Self { _mode: PhantomData } + } +} + +impl PinExt for Pin { + type Mode = MODE; + + #[inline(always)] + fn pin_id(&self) -> u8 { + N + } + #[inline(always)] + fn port_id(&self) -> u8 { + P as u8 - b'A' + } +} + +impl Pin, HL, P, N> { + /// Set pin speed + pub fn set_speed(self, speed: Speed) -> Self { + let offset = 2 * { N }; + + unsafe { + (*Gpio::

::ptr()) + .ospeedr + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | ((speed as u32) << offset))) + }; + + self + } +} + +impl Pin, HL, P, N> { + /// Enables / disables the internal pull up + pub fn internal_pull_up(&mut self, _pupdr: &mut PUPDR

, on: bool) { + let offset = 2 * { N }; + let value = if on { 0b01 } else { 0b00 }; + unsafe { + (*Gpio::

::ptr()) + .pupdr + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | (value << offset))) + }; + } +} + +impl Pin, HL, P, N> { + /// Set pin speed + pub fn set_speed(self, speed: Speed) -> Self { + let offset = 2 * { N }; + + unsafe { + (*Gpio::

::ptr()) + .ospeedr + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | ((speed as u32) << offset))) + }; + + self + } + + /// Enables / disables the internal pull up + pub fn internal_pull_up(&mut self, _pupdr: &mut PUPDR

, on: bool) { + let offset = 2 * { N }; + let value = if on { 0b01 } else { 0b00 }; + unsafe { + (*Gpio::

::ptr()) + .pupdr + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | (value << offset))) + }; + } +} + +impl Pin, HL, P, N> { + /// Turns pin alternate configuration pin into open drain + pub fn set_open_drain(self) -> Pin, HL, P, N> { + let offset = { N }; + unsafe { + (*Gpio::

::ptr()) + .otyper + .modify(|r, w| w.bits(r.bits() | (1 << offset))) + }; + + Pin::new() + } +} + +impl Pin { + /// Erases the pin number from the type + /// + /// This is useful when you want to collect the pins into an array where you + /// need all the elements to have the same type + pub fn erase_number(self) -> PEPin { + PEPin::new(N) + } + + /// Erases the pin number and the port from the type + /// + /// This is useful when you want to collect the pins into an array where you + /// need all the elements to have the same type + pub fn erase(self) -> EPin { + EPin::new(P as u8 - b'A', N) + } +} + +// Internal helper functions +// +// NOTE: The functions in this impl block are "safe", but they +// are callable when the pin is in modes where they don't make +// sense. +impl Pin { + /// Set the output of the pin regardless of its mode. + /// Primarily used to set the output value of the pin + /// before changing its mode to an output to avoid + /// a short spike of an incorrect value + #[inline(always)] + fn _set_state(&mut self, state: PinState) { + match state { + PinState::High => self._set_high(), + PinState::Low => self._set_low(), + } + } + #[inline(always)] + fn _set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*Gpio::

::ptr()).bsrr.write(|w| w.bits(1 << N)) } + } + #[inline(always)] + fn _set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*Gpio::

::ptr()).bsrr.write(|w| w.bits(1 << (16 + N))) } + } + + #[inline(always)] + fn _is_set_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).odr.read().bits() & (1 << N) == 0 } + } + #[inline(always)] + fn _is_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).idr.read().bits() & (1 << N) == 0 } + } +} + +impl Pin, HL, P, N> { + #[inline] + pub fn set_high(&mut self) { + self._set_high() + } + #[inline] + pub fn set_low(&mut self) { + self._set_low() + } + #[inline(always)] + pub fn get_state(&self) -> PinState { + if self._is_set_low() { + PinState::Low + } else { + PinState::High + } + } + #[inline(always)] + pub fn set_state(&mut self, state: PinState) { + match state { + PinState::Low => self._set_low(), + PinState::High => self._set_high(), + } + } + #[inline] + pub fn is_set_high(&self) -> bool { + !self._is_set_low() + } + #[inline] + pub fn is_set_low(&self) -> bool { + self._is_set_low() + } + #[inline] + pub fn toggle(&mut self) { + if self._is_set_low() { + self._set_high() + } else { + self._set_low() + } + } +} + +impl OutputPin for Pin, HL, P, N> { + type Error = Infallible; + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl StatefulOutputPin for Pin, HL, P, N> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl ToggleableOutputPin for Pin, HL, P, N> { + type Error = Infallible; + + #[inline(always)] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl Pin, HL, P, N> { + #[inline] + pub fn is_high(&self) -> bool { + !self._is_low() + } + #[inline] + pub fn is_low(&self) -> bool { + self._is_low() + } +} + +impl InputPin for Pin, HL, P, N> { + type Error = Infallible; + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl Pin, HL, P, N> { + #[inline] + pub fn is_high(&self) -> bool { + !self._is_low() + } + #[inline] + pub fn is_low(&self) -> bool { + self._is_low() + } +} + +impl InputPin for Pin, HL, P, N> { + type Error = Infallible; + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +/// Opaque AFR register +pub struct Afr { + _afr: PhantomData, +} + +impl Afr { + pub(crate) fn new() -> Self { + Self { _afr: PhantomData } + } +} + +macro_rules! af { + ($HL:ident, $AFR:ident, $afr:ident) => { + #[doc(hidden)] + pub struct $HL { + _0: (), + } + + impl Afr<$HL, P> { + #[allow(dead_code)] + pub(crate) fn afr(&mut self) -> &pac::gpioa::$AFR { + unsafe { &(*Gpio::

::ptr()).$afr } + } + } + }; +} + +af!(H8, AFRH, afrh); +af!(L8, AFRL, afrl); + +gpio!(GPIOA, gpioa, PAx, 'A', 0, [ + PA0: (pa0, 0, Analog, L8, exticr1), + PA1: (pa1, 1, Analog, L8, exticr1), + PA2: (pa2, 2, Analog, L8, exticr1), + PA3: (pa3, 3, Analog, L8, exticr1), + PA4: (pa4, 4, Analog, L8, exticr2), + PA5: (pa5, 5, Analog, L8, exticr2), + PA6: (pa6, 6, Analog, L8, exticr2), + PA7: (pa7, 7, Analog, L8, exticr2), + PA8: (pa8, 8, Analog, H8, exticr3), + PA9: (pa9, 9, Analog, H8, exticr3), + PA10: (pa10, 10, Analog, H8, exticr3), + PA11: (pa11, 11, Analog, H8, exticr3), + PA12: (pa12, 12, Analog, H8, exticr4), + PA13: (pa13, 13, super::Debugger, H8, exticr4), // SWDIO, PullUp VeryHigh speed + PA14: (pa14, 14, super::Debugger, H8, exticr4), // SWCLK, PullDown + PA15: (pa15, 15, super::Debugger, H8, exticr4), // JTDI, PullUp +]); + +gpio!(GPIOB, gpiob, PBx, 'B', 1, [ + PB0: (pb0, 0, Analog, L8, exticr1), + PB1: (pb1, 1, Analog, L8, exticr1), + PB2: (pb2, 2, Analog, L8, exticr1), + PB3: (pb3, 3, super::Debugger, L8, exticr1), // SWO + PB4: (pb4, 4, super::Debugger, L8, exticr2), // JTRST, PullUp + PB5: (pb5, 5, Analog, L8, exticr2), + PB6: (pb6, 6, Analog, L8, exticr2), + PB7: (pb7, 7, Analog, L8, exticr2), + PB8: (pb8, 8, Analog, H8, exticr3), + PB9: (pb9, 9, Analog, H8, exticr3), + PB10: (pb10, 10, Analog, H8, exticr3), + PB11: (pb11, 11, Analog, H8, exticr3), + PB12: (pb12, 12, Analog, H8, exticr4), + PB13: (pb13, 13, Analog, H8, exticr4), + PB14: (pb14, 14, Analog, H8, exticr4), + PB15: (pb15, 15, Analog, H8, exticr4), +]); + +gpio!(GPIOC, gpioc, PCx, 'C', 2, [ + PC0: (pc0, 0, Analog, L8, exticr1), + PC1: (pc1, 1, Analog, L8, exticr1), + PC2: (pc2, 2, Analog, L8, exticr1), + PC3: (pc3, 3, Analog, L8, exticr1), + PC4: (pc4, 4, Analog, L8, exticr2), + PC5: (pc5, 5, Analog, L8, exticr2), + PC6: (pc6, 6, Analog, L8, exticr2), + PC7: (pc7, 7, Analog, L8, exticr2), + PC8: (pc8, 8, Analog, H8, exticr3), + PC9: (pc9, 9, Analog, H8, exticr3), + PC10: (pc10, 10, Analog, H8, exticr3), + PC11: (pc11, 11, Analog, H8, exticr3), + PC12: (pc12, 12, Analog, H8, exticr4), + PC13: (pc13, 13, Analog, H8, exticr4), + PC14: (pc14, 14, Analog, H8, exticr4), + PC15: (pc15, 15, Analog, H8, exticr4), +]); + +gpio!(GPIOD, gpiod, PDx, 'D', 3, [ + PD0: (pd0, 0, Analog, L8, exticr1), + PD1: (pd1, 1, Analog, L8, exticr1), + PD2: (pd2, 2, Analog, L8, exticr1), + PD3: (pd3, 3, Analog, L8, exticr1), + PD4: (pd4, 4, Analog, L8, exticr2), + PD5: (pd5, 5, Analog, L8, exticr2), + PD6: (pd6, 6, Analog, L8, exticr2), + PD7: (pd7, 7, Analog, L8, exticr2), + PD8: (pd8, 8, Analog, H8, exticr3), + PD9: (pd9, 9, Analog, H8, exticr3), + PD10: (pd10, 10, Analog, H8, exticr3), + PD11: (pd11, 11, Analog, H8, exticr3), + PD12: (pd12, 12, Analog, H8, exticr4), + PD13: (pd13, 13, Analog, H8, exticr4), + PD14: (pd14, 14, Analog, H8, exticr4), + PD15: (pd15, 15, Analog, H8, exticr4), +]); + +gpio!(GPIOE, gpioe, PEx, 'E', 4, [ + PE0: (pe0, 0, Analog, L8, exticr1), + PE1: (pe1, 1, Analog, L8, exticr1), + PE2: (pe2, 2, Analog, L8, exticr1), + PE3: (pe3, 3, Analog, L8, exticr1), + PE4: (pe4, 4, Analog, L8, exticr2), + PE5: (pe5, 5, Analog, L8, exticr2), + PE6: (pe6, 6, Analog, L8, exticr2), + PE7: (pe7, 7, Analog, L8, exticr2), + PE8: (pe8, 8, Analog, H8, exticr3), + PE9: (pe9, 9, Analog, H8, exticr3), + PE10: (pe10, 10, Analog, H8, exticr3), + PE11: (pe11, 11, Analog, H8, exticr3), + PE12: (pe12, 12, Analog, H8, exticr4), + PE13: (pe13, 13, Analog, H8, exticr4), + PE14: (pe14, 14, Analog, H8, exticr4), + PE15: (pe15, 15, Analog, H8, exticr4), +]); + +#[cfg(any( + // feature = "stm32l471", // missing PAC support for Port G + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +gpio!(GPIOF, gpiof, PFx, 'F', 5, [ + PF0: (pf0, 0, Analog, L8, exticr1), + PF1: (pf1, 1, Analog, L8, exticr1), + PF2: (pf2, 2, Analog, L8, exticr1), + PF3: (pf3, 3, Analog, L8, exticr1), + PF4: (pf4, 4, Analog, L8, exticr2), + PF5: (pf5, 5, Analog, L8, exticr2), + PF6: (pf6, 6, Analog, L8, exticr2), + PF7: (pf7, 7, Analog, L8, exticr2), + PF8: (pf8, 8, Analog, H8, exticr3), + PF9: (pf9, 9, Analog, H8, exticr3), + PF10: (pf10, 10, Analog, H8, exticr3), + PF11: (pf11, 11, Analog, H8, exticr3), + PF12: (pf12, 12, Analog, H8, exticr4), + PF13: (pf13, 13, Analog, H8, exticr4), + PF14: (pf14, 14, Analog, H8, exticr4), + PF15: (pf15, 15, Analog, H8, exticr4), +]); +#[cfg(any( + // feature = "stm32l471", // missing PAC support for Port G + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +gpio!(GPIOG, gpiog, PGx, 'G', 6, + { unsafe { (*crate::pac::PWR::ptr()).cr2.modify(|_,w| w.iosv().set_bit()); } }, +[ + PG0: (pg0, 0, Analog, L8, exticr1), + PG1: (pg1, 1, Analog, L8, exticr1), + PG2: (pg2, 2, Analog, L8, exticr1), + PG3: (pg3, 3, Analog, L8, exticr1), + PG4: (pg4, 4, Analog, L8, exticr2), + PG5: (pg5, 5, Analog, L8, exticr2), + PG6: (pg6, 6, Analog, L8, exticr2), + PG7: (pg7, 7, Analog, L8, exticr2), + PG8: (pg8, 8, Analog, H8, exticr3), + PG9: (pg9, 9, Analog, H8, exticr3), + PG10: (pg10, 10, Analog, H8, exticr3), + PG11: (pg11, 11, Analog, H8, exticr3), + PG12: (pg12, 12, Analog, H8, exticr4), + PG13: (pg13, 13, Analog, H8, exticr4), + PG14: (pg14, 14, Analog, H8, exticr4), + PG15: (pg15, 15, Analog, H8, exticr4), +]); + +struct Gpio; +impl Gpio

{ + const fn ptr() -> *const crate::pac::gpioa::RegisterBlock { + match P { + 'A' => crate::pac::GPIOA::ptr(), + 'B' => crate::pac::GPIOB::ptr() as _, + 'C' => crate::pac::GPIOC::ptr() as _, + 'D' => crate::pac::GPIOD::ptr() as _, + 'E' => crate::pac::GPIOE::ptr() as _, + #[cfg(any( + // feature = "stm32l471", // missing PAC support for Port F + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", + ))] + 'F' => crate::pac::GPIOF::ptr() as _, + #[cfg(any( + // feature = "stm32l471", // missing PAC support for Port G + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", + ))] + 'G' => crate::pac::GPIOG::ptr() as _, + _ => crate::pac::GPIOA::ptr(), + } + } +} diff --git a/stm32l4xx-hal/src/gpio/convert.rs b/stm32l4xx-hal/src/gpio/convert.rs new file mode 100644 index 0000000..d962114 --- /dev/null +++ b/stm32l4xx-hal/src/gpio/convert.rs @@ -0,0 +1,362 @@ +use super::*; + +/// Const assert hack +struct Assert; + +impl Assert { + pub const LESS: u8 = R - L - 1; +} + +impl Pin { + fn set_alternate(&mut self) { + #[allow(path_statements, clippy::no_effect)] + { + Assert::::LESS; + } + let offset = 2 * { N }; + unsafe { + if N < 8 { + let offset2 = 4 * { N }; + (*Gpio::

::ptr()).afrl.modify(|r, w| { + w.bits((r.bits() & !(0b1111 << offset2)) | ((A as u32) << offset2)) + }); + } else { + let offset2 = 4 * { N - 8 }; + (*Gpio::

::ptr()).afrh.modify(|r, w| { + w.bits((r.bits() & !(0b1111 << offset2)) | ((A as u32) << offset2)) + }); + } + (*Gpio::

::ptr()) + .moder + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | (0b10 << offset))); + } + } + /// Configures the pin to operate alternate mode + pub fn into_alternate( + mut self, + _moder: &mut MODER

, + _otyper: &mut OTYPER

, + _afr: &mut Afr, + ) -> Pin, HL, P, N> { + self.set_alternate::(); + Pin::new() + } + + /// Configures the pin to operate alternate mode (alias for `into_alternate`) + pub fn into_alternate_push_pull( + self, + moder: &mut MODER

, + otyper: &mut OTYPER

, + afr: &mut Afr, + ) -> Pin, HL, P, N> { + self.into_alternate::(moder, otyper, afr) + } + + /// Configures the pin to operate in alternate open drain mode + #[allow(path_statements)] + pub fn into_alternate_open_drain( + self, + moder: &mut MODER

, + otyper: &mut OTYPER

, + afr: &mut Afr, + ) -> Pin, HL, P, N> { + self.into_alternate::(moder, otyper, afr) + .set_open_drain() + } + + /// Configures the pin to operate as a floating input pin + pub fn into_floating_input( + mut self, + _moder: &mut MODER

, + _pupdr: &mut PUPDR

, + ) -> Pin, HL, P, N> { + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as a pulled down input pin + pub fn into_pull_down_input( + mut self, + _moder: &mut MODER

, + _pupdr: &mut PUPDR

, + ) -> Pin, HL, P, N> { + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as a pulled up input pin + pub fn into_pull_up_input( + mut self, + _moder: &mut MODER

, + _pupdr: &mut PUPDR

, + ) -> Pin, HL, P, N> { + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as an open drain output pin + /// Initial state will be low. + pub fn into_open_drain_output( + mut self, + _moder: &mut MODER

, + _otyper: &mut OTYPER

, + ) -> Pin, HL, P, N> { + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as an open-drain output pin. + /// `initial_state` specifies whether the pin should be initially high or low. + pub fn into_open_drain_output_in_state( + mut self, + _moder: &mut MODER

, + _otyper: &mut OTYPER

, + initial_state: PinState, + ) -> Pin, HL, P, N> { + self._set_state(initial_state); + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as an push pull output pin + /// Initial state will be low. + pub fn into_push_pull_output( + mut self, + _moder: &mut MODER

, + _otyper: &mut OTYPER

, + ) -> Pin, HL, P, N> { + self._set_low(); + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as an push-pull output pin. + /// `initial_state` specifies whether the pin should be initially high or low. + pub fn into_push_pull_output_in_state( + mut self, + _moder: &mut MODER

, + _otyper: &mut OTYPER

, + initial_state: PinState, + ) -> Pin, HL, P, N> { + self._set_state(initial_state); + self.mode::>(); + Pin::new() + } + + /// Configures the pin to operate as an analog input pin + pub fn into_analog( + mut self, + _moder: &mut MODER

, + _pupdr: &mut PUPDR

, + ) -> Pin { + self.mode::(); + Pin::new() + } + + /// Puts `self` into mode `M`. + /// + /// This violates the type state constraints from `MODE`, so callers must + /// ensure they use this properly. + #[inline(always)] + fn mode(&mut self) { + let offset = 2 * N; + unsafe { + (*Gpio::

::ptr()) + .pupdr + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | (M::PUPDR << offset))); + + if let Some(otyper) = M::OTYPER { + (*Gpio::

::ptr()) + .otyper + .modify(|r, w| w.bits(r.bits() & !(0b1 << N) | (otyper << N))); + } + + (*Gpio::

::ptr()) + .moder + .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | (M::MODER << offset))); + } + } +} + +impl Pin +where + MODE: PinMode, +{ + fn with_mode(&mut self, f: F) -> R + where + M: PinMode, + F: FnOnce(&mut Pin) -> R, + { + self.mode::(); + + // This will reset the pin back to the original mode when dropped. + // (so either when `with_mode` returns or when `f` unwinds) + let _resetti = ResetMode { pin: self }; + + let mut witness = Pin::new(); + + f(&mut witness) + } + + /// Temporarily configures this pin as a floating input. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + pub fn with_floating_input( + &mut self, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as a pulled-down input. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + pub fn with_pull_down_input( + &mut self, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as a pulled-up input. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + pub fn with_pull_up_input( + &mut self, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as an analog pin. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + pub fn with_analog(&mut self, f: impl FnOnce(&mut Pin) -> R) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as an open drain output. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// The value of the pin after conversion is undefined. If you + /// want to control it, use `with_open_drain_output_in_state` + pub fn with_open_drain_output( + &mut self, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as an open drain output . + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// Note that the new state is set slightly before conversion + /// happens. This can cause a short output glitch if switching + /// between output modes + pub fn with_open_drain_output_in_state( + &mut self, + state: PinState, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self._set_state(state); + self.with_mode(f) + } + + /// Temporarily configures this pin as a push-pull output. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// The value of the pin after conversion is undefined. If you + /// want to control it, use `with_push_pull_output_in_state` + pub fn with_push_pull_output( + &mut self, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as a push-pull output. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// Note that the new state is set slightly before conversion + /// happens. This can cause a short output glitch if switching + /// between output modes + pub fn with_push_pull_output_in_state( + &mut self, + state: PinState, + f: impl FnOnce(&mut Pin, HL, P, N>) -> R, + ) -> R { + self._set_state(state); + self.with_mode(f) + } +} + +struct ResetMode<'a, ORIG: PinMode, HL, const P: char, const N: u8> { + pin: &'a mut Pin, +} + +impl<'a, ORIG: PinMode, HL, const P: char, const N: u8> Drop for ResetMode<'a, ORIG, HL, P, N> { + fn drop(&mut self) { + self.pin.mode::(); + } +} + +/// Marker trait for valid pin modes (type state). +/// +/// It can not be implemented by outside types. +pub trait PinMode: crate::Sealed { + // These constants are used to implement the pin configuration code. + // They are not part of public API. + + #[doc(hidden)] + const PUPDR: u32; + #[doc(hidden)] + const MODER: u32; + #[doc(hidden)] + const OTYPER: Option = None; +} + +impl crate::Sealed for Input {} +impl PinMode for Input { + const PUPDR: u32 = 0b00; + const MODER: u32 = 0b00; +} + +impl crate::Sealed for Input {} +impl PinMode for Input { + const PUPDR: u32 = 0b10; + const MODER: u32 = 0b00; +} + +impl crate::Sealed for Input {} +impl PinMode for Input { + const PUPDR: u32 = 0b01; + const MODER: u32 = 0b00; +} + +impl crate::Sealed for Analog {} +impl PinMode for Analog { + const PUPDR: u32 = 0b00; + const MODER: u32 = 0b11; +} + +impl crate::Sealed for Output {} +impl PinMode for Output { + const PUPDR: u32 = 0b00; + const MODER: u32 = 0b01; + const OTYPER: Option = Some(0b1); +} + +impl crate::Sealed for Output {} +impl PinMode for Output { + const PUPDR: u32 = 0b00; + const MODER: u32 = 0b01; + const OTYPER: Option = Some(0b0); +} diff --git a/stm32l4xx-hal/src/gpio/erased.rs b/stm32l4xx-hal/src/gpio/erased.rs new file mode 100644 index 0000000..f6416c6 --- /dev/null +++ b/stm32l4xx-hal/src/gpio/erased.rs @@ -0,0 +1,198 @@ +use super::*; + +pub type EPin = ErasedPin; + +/// Fully erased pin +/// +/// `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section). +pub struct ErasedPin { + // Bits 0-3: Pin, Bits 4-7: Port + pin_port: u8, + _mode: PhantomData, +} + +impl PinExt for ErasedPin { + type Mode = MODE; + + #[inline(always)] + fn pin_id(&self) -> u8 { + self.pin_port & 0x0f + } + #[inline(always)] + fn port_id(&self) -> u8 { + self.pin_port >> 4 + } +} + +impl ErasedPin { + pub(crate) fn new(port: u8, pin: u8) -> Self { + Self { + pin_port: port << 4 | pin, + _mode: PhantomData, + } + } + + #[inline] + fn block(&self) -> &crate::pac::gpioa::RegisterBlock { + // This function uses pointer arithmetic instead of branching to be more efficient + + // The logic relies on the following assumptions: + // - GPIOA register is available on all chips + // - all gpio register blocks have the same layout + // - consecutive gpio register blocks have the same offset between them, namely 0x0400 + // - ErasedPin::new was called with a valid port + + // FIXME could be calculated after const_raw_ptr_to_usize_cast stabilization #51910 + const GPIO_REGISTER_OFFSET: usize = 0x0400; + + let offset = GPIO_REGISTER_OFFSET * self.port_id() as usize; + let block_ptr = + (crate::pac::GPIOA::ptr() as usize + offset) as *const crate::pac::gpioa::RegisterBlock; + + unsafe { &*block_ptr } + } +} + +impl ErasedPin> { + #[inline(always)] + pub fn set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { self.block().bsrr.write(|w| w.bits(1 << self.pin_id())) }; + } + + #[inline(always)] + pub fn set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { + self.block() + .bsrr + .write(|w| w.bits(1 << (self.pin_id() + 16))) + }; + } + + #[inline(always)] + pub fn get_state(&self) -> PinState { + if self.is_set_low() { + PinState::Low + } else { + PinState::High + } + } + + #[inline(always)] + pub fn set_state(&mut self, state: PinState) { + match state { + PinState::Low => self.set_low(), + PinState::High => self.set_high(), + } + } + + #[inline(always)] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + #[inline(always)] + pub fn is_set_low(&self) -> bool { + self.block().odr.read().bits() & (1 << self.pin_id()) == 0 + } + + #[inline(always)] + pub fn toggle(&mut self) { + if self.is_set_low() { + self.set_high() + } else { + self.set_low() + } + } +} + +impl OutputPin for ErasedPin> { + type Error = core::convert::Infallible; + + #[inline(always)] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline(always)] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl StatefulOutputPin for ErasedPin> { + #[inline(always)] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + #[inline(always)] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl ToggleableOutputPin for ErasedPin> { + type Error = Infallible; + + #[inline(always)] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl ErasedPin> { + #[inline(always)] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + #[inline(always)] + pub fn is_low(&self) -> bool { + self.block().idr.read().bits() & (1 << self.pin_id()) == 0 + } +} + +impl InputPin for ErasedPin> { + type Error = core::convert::Infallible; + + #[inline(always)] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl ErasedPin> { + #[inline(always)] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + #[inline(always)] + pub fn is_low(&self) -> bool { + self.block().idr.read().bits() & (1 << self.pin_id()) == 0 + } +} + +impl InputPin for ErasedPin> { + type Error = core::convert::Infallible; + + #[inline(always)] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} diff --git a/stm32l4xx-hal/src/gpio/partially_erased.rs b/stm32l4xx-hal/src/gpio/partially_erased.rs new file mode 100644 index 0000000..79f344c --- /dev/null +++ b/stm32l4xx-hal/src/gpio/partially_erased.rs @@ -0,0 +1,181 @@ +use super::*; + +pub type PEPin = PartiallyErasedPin; + +/// Partially erased pin +/// +/// - `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section). +/// - `P` is port name: `A` for GPIOA, `B` for GPIOB, etc. +pub struct PartiallyErasedPin { + i: u8, + _mode: PhantomData, +} + +impl PartiallyErasedPin { + pub(crate) fn new(i: u8) -> Self { + Self { + i, + _mode: PhantomData, + } + } +} + +impl PinExt for PartiallyErasedPin { + type Mode = MODE; + + #[inline(always)] + fn pin_id(&self) -> u8 { + self.i + } + #[inline(always)] + fn port_id(&self) -> u8 { + P as u8 - b'A' + } +} + +impl PartiallyErasedPin, P> { + #[inline(always)] + pub fn set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*Gpio::

::ptr()).bsrr.write(|w| w.bits(1 << self.i)) } + } + + #[inline(always)] + pub fn set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { + (*Gpio::

::ptr()) + .bsrr + .write(|w| w.bits(1 << (self.i + 16))) + } + } + + #[inline(always)] + pub fn get_state(&self) -> PinState { + if self.is_set_low() { + PinState::Low + } else { + PinState::High + } + } + + #[inline(always)] + pub fn set_state(&mut self, state: PinState) { + match state { + PinState::Low => self.set_low(), + PinState::High => self.set_high(), + } + } + + #[inline(always)] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + #[inline(always)] + pub fn is_set_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).odr.read().bits() & (1 << self.i) == 0 } + } + + #[inline(always)] + pub fn toggle(&mut self) { + if self.is_set_low() { + self.set_high() + } else { + self.set_low() + } + } +} + +impl OutputPin for PartiallyErasedPin, P> { + type Error = Infallible; + + #[inline(always)] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline(always)] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl StatefulOutputPin for PartiallyErasedPin, P> { + #[inline(always)] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + #[inline(always)] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl ToggleableOutputPin for PartiallyErasedPin, P> { + type Error = Infallible; + + #[inline(always)] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl PartiallyErasedPin, P> { + #[inline(always)] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + #[inline(always)] + pub fn is_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).idr.read().bits() & (1 << self.i) == 0 } + } +} + +impl InputPin for PartiallyErasedPin, P> { + type Error = Infallible; + + #[inline(always)] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl PartiallyErasedPin, P> { + #[inline(always)] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + #[inline(always)] + pub fn is_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).idr.read().bits() & (1 << self.i) == 0 } + } +} + +impl InputPin for PartiallyErasedPin, P> { + type Error = Infallible; + + #[inline(always)] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} diff --git a/stm32l4xx-hal/src/i2c.rs b/stm32l4xx-hal/src/i2c.rs new file mode 100644 index 0000000..9fae79f --- /dev/null +++ b/stm32l4xx-hal/src/i2c.rs @@ -0,0 +1,645 @@ +//! Inter-Integrated Circuit (I2C) bus. Synchronized with the +//! [stm32h7xx-hal](https://github.com/stm32-rs/stm32h7xx-hal) implementation, +//! as of 2021-02-25. + +use crate::hal::blocking::i2c::{Read, Write, WriteRead}; + +#[cfg(any( + feature = "stm32l451", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +use crate::pac::I2C4; +use crate::pac::{i2c1, I2C1, I2C2, I2C3}; + +use crate::rcc::{Clocks, Enable, RccBus, Reset}; +use crate::time::Hertz; +use cast::{u16, u8}; +use core::ops::Deref; + +/// I2C error +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// Bus error + Bus, + /// Arbitration loss + Arbitration, + /// NACK + Nack, + // Overrun, // slave mode only + // Pec, // SMBUS mode only + // Timeout, // SMBUS mode only + // Alert, // SMBUS mode only +} + +#[doc(hidden)] +pub(self) mod private { + pub trait Sealed {} +} + +/// SCL pin. This trait is sealed and cannot be implemented. +pub trait SclPin: private::Sealed {} + +/// SDA pin. This trait is sealed and cannot be implemented. +pub trait SdaPin: private::Sealed {} + +macro_rules! pins { + ($spi:ident, $af:literal, SCL: [$($scl:ident),*], SDA: [$($sda:ident),*]) => { + $( + impl super::private::Sealed for $scl> {} + impl super::SclPin<$spi> for $scl> {} + )* + $( + impl super::private::Sealed for $sda> {} + impl super::SdaPin<$spi> for $sda> {} + )* + } +} + +/// I2C peripheral operating in master mode +pub struct I2c { + i2c: I2C, + pins: PINS, +} + +pub struct Config { + presc: u8, + sclh: u8, + scll: u8, + scldel: u8, + sdadel: u8, +} + +impl Config { + pub fn new(freq: Hertz, clocks: Clocks) -> Self { + let freq = freq.raw(); + assert!(freq <= 1_000_000); + + // TODO review compliance with the timing requirements of I2C + // t_I2CCLK = 1 / PCLK1 + // t_PRESC = (PRESC + 1) * t_I2CCLK + // t_SCLL = (SCLL + 1) * t_PRESC + // t_SCLH = (SCLH + 1) * t_PRESC + // + // t_SYNC1 + t_SYNC2 > 4 * t_I2CCLK + // t_SCL ~= t_SYNC1 + t_SYNC2 + t_SCLL + t_SCLH + let i2cclk = clocks.pclk1().raw(); + let ratio = i2cclk / freq - 4; + let (presc, scll, sclh, sdadel, scldel) = if freq >= 100_000 { + // fast-mode or fast-mode plus + // here we pick SCLL + 1 = 2 * (SCLH + 1) + let presc = ratio / 387; + + let sclh = ((ratio / (presc + 1)) - 3) / 3; + let scll = 2 * (sclh + 1) - 1; + + let (sdadel, scldel) = if freq > 400_000 { + // fast-mode plus + let sdadel = 0; + let scldel = i2cclk / 4_000_000 / (presc + 1) - 1; + + (sdadel, scldel) + } else { + // fast-mode + let sdadel = i2cclk / 8_000_000 / (presc + 1); + let scldel = i2cclk / 2_000_000 / (presc + 1) - 1; + + (sdadel, scldel) + }; + + (presc, scll, sclh, sdadel, scldel) + } else { + // standard-mode + // here we pick SCLL = SCLH + let presc = ratio / 514; + + let sclh = ((ratio / (presc + 1)) - 2) / 2; + let scll = sclh; + + let sdadel = i2cclk / 2_000_000 / (presc + 1); + let scldel = i2cclk / 800_000 / (presc + 1) - 1; + + (presc, scll, sclh, sdadel, scldel) + }; + + macro_rules! u8_or_panic { + ($value: expr, $message: literal) => { + match u8($value) { + Ok(value) => value, + Err(_) => panic!($message), + } + }; + } + + let presc = u8_or_panic!(presc, "I2C pres"); + assert!(presc < 16); + + let scldel = u8_or_panic!(scldel, "I2C scldel"); + assert!(scldel < 16); + + let sdadel = u8_or_panic!(sdadel, "I2C sdadel"); + assert!(sdadel < 16); + + let sclh = u8_or_panic!(sclh, "I2C sclh"); + let scll = u8_or_panic!(scll, "I2C scll"); + + Self { + presc, + sclh, + scll, + scldel, + sdadel, + } + } + + /// For the layout of `timing_bits`, see RM0394 section 37.7.5. + pub fn with_timing(timing_bits: u32) -> Self { + Self { + presc: ((timing_bits >> 28) & 0xf) as u8, + scldel: ((timing_bits >> 20) & 0xf) as u8, + sdadel: ((timing_bits >> 16) & 0xf) as u8, + sclh: ((timing_bits >> 8) & 0xff) as u8, + scll: (timing_bits & 0xff) as u8, + } + } +} + +macro_rules! hal { + ($i2c_type: ident, $i2cX: ident) => { + impl I2c<$i2c_type, (SCL, SDA)> { + pub fn $i2cX( + i2c: $i2c_type, + pins: (SCL, SDA), + config: Config, + apb1: &mut <$i2c_type as RccBus>::Bus, + ) -> Self + where + SCL: SclPin<$i2c_type>, + SDA: SdaPin<$i2c_type>, + { + <$i2c_type>::enable(apb1); + <$i2c_type>::reset(apb1); + Self::new(i2c, pins, config) + } + } + }; +} + +hal!(I2C1, i2c1); +hal!(I2C2, i2c2); +hal!(I2C3, i2c3); + +#[cfg(any( + feature = "stm32l451", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +hal!(I2C4, i2c4); + +impl I2c +where + I2C: Deref, +{ + /// Configures the I2C peripheral to work in master mode + fn new(i2c: I2C, pins: (SCL, SDA), config: Config) -> Self + where + SCL: SclPin, + SDA: SdaPin, + { + // Make sure the I2C unit is disabled so we can configure it + i2c.cr1.modify(|_, w| w.pe().clear_bit()); + // Configure for "fast mode" (400 KHz) + i2c.timingr.write(|w| { + w.presc() + .bits(config.presc) + .scll() + .bits(config.scll) + .sclh() + .bits(config.sclh) + .sdadel() + .bits(config.sdadel) + .scldel() + .bits(config.scldel) + }); + + // Enable the peripheral + i2c.cr1.write(|w| w.pe().set_bit()); + + I2c { i2c, pins } + } + + /// Releases the I2C peripheral and associated pins + pub fn free(self) -> (I2C, (SCL, SDA)) { + (self.i2c, self.pins) + } +} + +/// Sequence to flush the TXDR register. This resets the TXIS and TXE +// flags +macro_rules! flush_txdr { + ($i2c:expr) => { + // If a pending TXIS flag is set, write dummy data to TXDR + if $i2c.isr.read().txis().bit_is_set() { + $i2c.txdr.write(|w| w.txdata().bits(0)); + } + + // If TXDR is not flagged as empty, write 1 to flush it + if $i2c.isr.read().txe().is_not_empty() { + $i2c.isr.write(|w| w.txe().set_bit()); + } + }; +} + +macro_rules! busy_wait { + ($i2c:expr, $flag:ident, $variant:ident) => { + loop { + let isr = $i2c.isr.read(); + + if isr.$flag().$variant() { + break; + } else if isr.berr().is_error() { + $i2c.icr.write(|w| w.berrcf().set_bit()); + return Err(Error::Bus); + } else if isr.arlo().is_lost() { + $i2c.icr.write(|w| w.arlocf().set_bit()); + return Err(Error::Arbitration); + } else if isr.nackf().bit_is_set() { + $i2c.icr.write(|w| w.stopcf().set_bit().nackcf().set_bit()); + flush_txdr!($i2c); + return Err(Error::Nack); + } else { + // try again + } + } + }; +} + +impl Write for I2c +where + I2C: Deref, +{ + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + // TODO support transfers of more than 255 bytes + assert!(bytes.len() < 256); + + // Wait for any previous address sequence to end + // automatically. This could be up to 50% of a bus + // cycle (ie. up to 0.5/freq) + while self.i2c.cr2.read().start().bit_is_set() {} + + // Set START and prepare to send `bytes`. The + // START bit can be set even if the bus is BUSY or + // I2C is in slave mode. + self.i2c.cr2.write(|w| { + w.start() + .set_bit() + .sadd() + .bits(u16(addr << 1 | 0)) + .add10() + .clear_bit() + .rd_wrn() + .write() + .nbytes() + .bits(bytes.len() as u8) + .autoend() + .software() + }); + + for byte in bytes { + // Wait until we are allowed to send data + // (START has been ACKed or last byte when + // through) + busy_wait!(self.i2c, txis, is_empty); + + // Put byte on the wire + self.i2c.txdr.write(|w| w.txdata().bits(*byte)); + } + + // Wait until the write finishes + busy_wait!(self.i2c, tc, is_complete); + + // Stop + self.i2c.cr2.write(|w| w.stop().set_bit()); + + Ok(()) + // Tx::new(&self.i2c)?.write(addr, bytes) + } +} + +impl Read for I2c +where + I2C: Deref, +{ + type Error = Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + // TODO support transfers of more than 255 bytes + assert!(buffer.len() < 256 && buffer.len() > 0); + + // Wait for any previous address sequence to end + // automatically. This could be up to 50% of a bus + // cycle (ie. up to 0.5/freq) + while self.i2c.cr2.read().start().bit_is_set() {} + + // Set START and prepare to receive bytes into + // `buffer`. The START bit can be set even if the bus + // is BUSY or I2C is in slave mode. + self.i2c.cr2.write(|w| { + w.sadd() + .bits((addr << 1 | 0) as u16) + .rd_wrn() + .read() + .nbytes() + .bits(buffer.len() as u8) + .start() + .set_bit() + .autoend() + .automatic() + }); + + for byte in buffer { + // Wait until we have received something + busy_wait!(self.i2c, rxne, is_not_empty); + + *byte = self.i2c.rxdr.read().rxdata().bits(); + } + + // automatic STOP + + Ok(()) + // Rx::new(&self.i2c)?.read(addr, buffer) + } +} + +impl WriteRead for I2c +where + I2C: Deref, +{ + type Error = Error; + + fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + // TODO support transfers of more than 255 bytes + assert!(bytes.len() < 256 && bytes.len() > 0); + assert!(buffer.len() < 256 && buffer.len() > 0); + + // Wait for any previous address sequence to end + // automatically. This could be up to 50% of a bus + // cycle (ie. up to 0.5/freq) + while self.i2c.cr2.read().start().bit_is_set() {} + + // Set START and prepare to send `bytes`. The + // START bit can be set even if the bus is BUSY or + // I2C is in slave mode. + self.i2c.cr2.write(|w| { + w.start() + .set_bit() + .sadd() + .bits(u16(addr << 1 | 0)) + .add10() + .clear_bit() + .rd_wrn() + .write() + .nbytes() + .bits(bytes.len() as u8) + .autoend() + .software() + }); + + for byte in bytes { + // Wait until we are allowed to send data + // (START has been ACKed or last byte went through) + busy_wait!(self.i2c, txis, is_empty); + + // Put byte on the wire + self.i2c.txdr.write(|w| w.txdata().bits(*byte)); + } + + // Wait until the write finishes before beginning to read. + busy_wait!(self.i2c, tc, is_complete); + + // reSTART and prepare to receive bytes into `buffer` + self.i2c.cr2.write(|w| { + w.sadd() + .bits(u16(addr << 1 | 1)) + .add10() + .clear_bit() + .rd_wrn() + .read() + .nbytes() + .bits(buffer.len() as u8) + .start() + .set_bit() + .autoend() + .automatic() + }); + + for byte in buffer { + // Wait until we have received something + busy_wait!(self.i2c, rxne, is_not_empty); + + *byte = self.i2c.rxdr.read().rxdata().bits(); + } + + Ok(()) + } +} + +#[cfg(any(feature = "stm32l431", feature = "stm32l451", feature = "stm32l471"))] +mod stm32l4x1_pins { + #[cfg(any(feature = "stm32l451"))] + use super::I2C4; + use super::{I2C1, I2C2, I2C3}; + use crate::gpio::*; + #[cfg(not(feature = "stm32l471"))] + use gpioa::{PA10, PA7, PA9}; + #[cfg(not(feature = "stm32l471"))] + use gpiob::PB4; + use gpiob::{PB10, PB11, PB13, PB14, PB6, PB7, PB8, PB9}; + use gpioc::{PC0, PC1}; + + pins!(I2C1, 4, SCL: [PB6, PB8], SDA: [PB7, PB9]); + + #[cfg(not(feature = "stm32l471"))] + pins!(I2C1, 4, SCL: [PA9], SDA: [PA10]); + + pins!(I2C2, 4, SCL: [PB10, PB13], SDA: [PB11, PB14]); + + pins!(I2C3, 4, SCL: [PC0], SDA: [PC1]); + + #[cfg(not(feature = "stm32l471"))] + pins!(I2C3, 4, SCL: [PA7], SDA: [PB4]); + #[cfg(not(any(feature = "stm32l431", feature = "stm32l471")))] + pins!(I2C4, 4, SCL: [PD12], SDA: [PD13]); + #[cfg(not(any(feature = "stm32l431", feature = "stm32l471")))] + pins!(I2C4, 3, SCL: [PB10], SDA: [PB11]); +} + +#[cfg(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462" +))] +mod stm32l4x2_pins { + #[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] + use super::I2C2; + #[cfg(any(feature = "stm32l452", feature = "stm32l462"))] + use super::I2C4; + use super::{I2C1, I2C3}; + use crate::gpio::*; + use gpioa::{PA10, PA7, PA9}; + #[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] + use gpiob::{PB10, PB11, PB13, PB14, PB8, PB9}; + use gpiob::{PB4, PB6, PB7}; + #[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] + use gpioc::{PC0, PC1}; + + pins!(I2C1, 4, SCL: [PA9, PB6], SDA: [PA10, PB7]); + + #[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] + pins!(I2C1, 4, SCL: [PB8], SDA: [PB9]); + #[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] + pins!(I2C2, 4, SCL: [PB10, PB13], SDA: [PB11, PB14]); + + pins!(I2C3, 4, SCL: [PA7], SDA: [PB4]); + + #[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] + pins!(I2C3, 4, SCL: [PC0], SDA: [PC1]); + #[cfg(any(feature = "stm32l452", feature = "stm32l462"))] + pins!(I2C4, 2, SCL: [PC0], SDA: [PC1]); + #[cfg(any(feature = "stm32l452", feature = "stm32l462"))] + pins!(I2C4, 3, SCL: [PB10], SDA: [PB11]); + #[cfg(any(feature = "stm32l452", feature = "stm32l462"))] + pins!(I2C4, 4, SCL: [PD12], SDA: [PD13]); +} + +#[cfg(any(feature = "stm32l433", feature = "stm32l443"))] +mod stm32l4x3_pins { + use super::{I2C1, I2C2, I2C3}; + use crate::gpio::*; + use gpioa::{PA10, PA7, PA9}; + use gpiob::{PB10, PB11, PB13, PB14, PB4, PB6, PB7, PB8, PB9}; + use gpioc::{PC0, PC1}; + + pins!(I2C1, 4, SCL: [PA9, PB6, PB8], SDA: [PA10, PB7, PB9]); + + pins!(I2C2, 4, SCL: [PB10, PB13], SDA: [PB11, PB14]); + + pins!(I2C3, 4, SCL: [PA7, PC0], SDA: [PB4, PC1]); +} + +#[cfg(any(feature = "stm32l475"))] +mod stm32l4x5_pins { + use super::{I2C1, I2C2, I2C3}; + use crate::gpio::*; + use gpiob::{PB10, PB11, PB13, PB14, PB6, PB7, PB8, PB9}; + use gpioc::{PC0, PC1}; + + pins!(I2C1, 4, SCL: [PB6, PB8], SDA: [PB7, PB9]); + + pins!(I2C2, 4, SCL: [PB10, PB13], SDA: [PB11, PB14]); + + pins!(I2C3, 4, SCL: [PC0], SDA: [PC1]); +} + +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" +))] +mod stm32l4x6_pins { + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + use super::I2C4; + use super::{I2C1, I2C2, I2C3}; + use crate::gpio::*; + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + use gpioa::PA7; + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + use gpiob::PB4; + use gpiob::{PB10, PB11, PB13, PB14, PB6, PB7, PB8, PB9}; + use gpioc::{PC0, PC1}; + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + use gpiod::{PD12, PD13}; + use gpiof::{PF0, PF1}; + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + use gpiof::{PF14, PF15}; + use gpiog::{PG13, PG14, PG7, PG8}; + + pins!(I2C1, 4, SCL: [PB6, PB8], SDA: [PB7, PB9]); + + pins!(I2C2, 4, SCL: [PB10, PB13, PF1], SDA: [PB11, PB14, PF0]); + + pins!(I2C3, 4, SCL: [PC0, PG7, PG14], SDA: [PC1, PG8, PG13]); + + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + pins!(I2C3, 4, SCL: [PA7], SDA: [PB4]); + #[cfg(any(feature = "stm32l496", feature = "stm32l4a6"))] + pins!(I2C4, 4, SCL: [PD12, PF14], SDA: [PD13, PF15]); + + // These are present on STM32L496XX and STM32L4A6xG, but the + // PAC does not have gpioh, so we can't actually these pins + // Both not on STM32L486XX and STM32L476XX + // use gpioh::{PH4, PH5, PH7, PH8}; + // pins!(I2C2, AF4, SCL: [PH4], SDA: [PH5]); + // pins!(I2C3, AF4, SCL: [PH7], SDA: [PH8]); +} + +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +mod stm32l4r9_pins { + use super::{I2C1, I2C2, I2C3, I2C4}; + use crate::gpio::*; + use gpioa::PA7; + use gpiob::{PB10, PB11, PB13, PB14, PB4, PB6, PB7, PB8, PB9}; + use gpioc::{PC0, PC1, PC9}; + use gpiod::{PD12, PD13}; + use gpiof::{PF0, PF1, PF14, PF15}; + use gpiog::{PG13, PG14, PG7, PG8}; + // use gpioh::{PH4, PH5, PH7, PH8}; + + pins!(I2C1, 4, SCL: [PB6, PB8, PG14], SDA: [PB7, PB9, PG13]); + + pins!(I2C2, 4, SCL: [PB10, PB13, PF1], SDA: [PB11, PB14, PF0]); + // pins!(I2C2, 4, SCL: [PH4], SDA: [PH5]); + + pins!(I2C3, 4, SCL: [PA7, PC0, PG7], SDA: [PB4, PC1, PC9, PG8]); + // pins!(I2C3, 4, SCL: [PH7], SDA: [PH8]); + + pins!(I2C4, 3, SCL: [PB10], SDA: [PB11]); + pins!(I2C4, 3, SCL: [PD12, PF14], SDA: [PD13, PF15]); + pins!(I2C4, 5, SCL: [PB6], SDA: [PB7]); +} diff --git a/stm32l4xx-hal/src/lib.rs b/stm32l4xx-hal/src/lib.rs new file mode 100644 index 0000000..bde3737 --- /dev/null +++ b/stm32l4xx-hal/src/lib.rs @@ -0,0 +1,188 @@ +//! STM32L4 HAL implementation +//! +//! NOTE: This HAL implementation is under active development (as is the underlying +//! `embedded_hal` itself, together with its traits, some of which are unproven). +//! We follow the HAL implementation in +//! and pull in individual devices behind features - the goal is for this implementation +//! to become ubiquitous for the STM32L4 family of devices (well-tested, feature-complete, +//! well-documented). However! At this time, actual testing has only been performed for +//! the STM32L432KC microcontroller. Participation is of course very welcome! + +#![no_std] +#![allow(invalid_reference_casting)] + +#[cfg(not(any( + feature = "stm32l431", + feature = "stm32l451", + feature = "stm32l471", + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443", + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // note L4+ PAC support is mostly missing so other than r9/s9 these features don't actually exist yet + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + // these have PAC support. Hal integration is very slim though + feature = "stm32l4r9", + feature = "stm32l4s9" +)))] +compile_error!( + "\ +This crate requires one of the following features enabled: + stm32l431, stm32l451, stm32l471 + stm32l412, stm32l422, stm32l432, stm32l442, stm32l452, stm32l462 + stm32l433, stm32l443 + stm32l475, + stm32l476, stm32l486, stm32l496, stm32l4a6 + stm32l4r9, stm32l4s9 +" +); + +// the common lists of features to spec what a MCU is capable of +// lists are split by 3rd digit and 2nd digit groups + +// L4x1 +// any(feature = "stm32l431", feature = "stm32l451", feature = "stm32l471") +// L4x2 +// any(feature = "stm32l412", feature = "stm32l422", feature = "stm32l432", feature = "stm32l442", feature = "stm32l452", feature = "stm32l462") +// L4x3 +// any(feature = "stm32l433", feature = "stm32l443") +// L4x5 +// any(feature = "stm32l475") +// L4x6 +// any(feature = "stm32l476", feature = "stm32l486", feature = "stm32l496", , feature = "stm32l4a6") +// L4+x9 +// any(feature = "stm32l4r9", feature = "stm32l4s9") + +// NOTE: The even member of the pair has additional hashing peripheral(s) +// L41 / L42 +// any(feature = "stm32l412", feature = "stm32l422") +// L43 / L44 +// any(feature = "stm32l431", feature = "stm32l432", feature = "stm32l433", feature = "stm32l442", feature = "stm32l443") +// L45 / L46 +// any(feature = "stm32l451", feature = "stm32l452", feature = "stm32l462") +// L47 / L48 +// any(feature = "stm32l471", feature = "stm32l475", feature = "stm32l476", feature = "stm32l486") +// L49 / L4a +// any(feature = "stm32l496", feature = "stm32l4a6") +// L4r / L4s +// any(feature = "stm32l4r9", feature = "stm32l4s9") + +pub use embedded_hal as hal; + +pub use stm32l4; +#[cfg(any(feature = "stm32l431", feature = "stm32l451", feature = "stm32l471"))] +pub use stm32l4::stm32l4x1 as pac; + +#[cfg(any(feature = "stm32l412", feature = "stm32l422"))] +pub use stm32l4::stm32l412 as pac; +#[cfg(any( + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462" +))] +pub use stm32l4::stm32l4x2 as pac; + +#[cfg(any(feature = "stm32l433", feature = "stm32l443"))] +pub use stm32l4::stm32l4x3 as pac; + +#[cfg(any(feature = "stm32l475"))] +pub use stm32l4::stm32l4x5 as pac; + +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" +))] +pub use stm32l4::stm32l4x6 as pac; + +#[cfg(any(feature = "stm32l4r9", feature = "stm32l4s9",))] +pub use stm32l4::stm32l4r9 as pac; + +#[cfg(feature = "rt")] +pub use self::pac::interrupt; +// aliases for crate::pac +pub use crate::pac as device; +pub use crate::pac as stm32; + +pub mod traits; + +#[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] +pub mod adc; +#[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] +#[cfg(not(any(feature = "stm32l412",)))] +pub mod can; +pub mod crc; +pub mod datetime; +pub mod delay; +pub mod dma; +pub mod dmamux; +pub mod flash; +pub mod gpio; +pub mod i2c; +pub mod lptimer; +#[cfg(all( + feature = "otg_fs", + any( + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + ) +))] +pub mod otg_fs; +pub mod prelude; +pub mod pwm; +pub mod pwr; +#[cfg(not(any( + feature = "stm32l433", + feature = "stm32l443", + feature = "stm32l4r9", + feature = "stm32l4s9", +)))] +pub mod qspi; +pub mod rcc; +pub mod rng; +pub mod rtc; +pub mod serial; +pub mod signature; +pub mod spi; +pub mod time; +pub mod timer; +pub mod tsc; +#[cfg(all( + feature = "stm32-usbd", + any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443" + ) +))] +pub mod usb; +pub mod watchdog; + +mod sealed { + pub trait Sealed {} +} +pub(crate) use sealed::Sealed; diff --git a/stm32l4xx-hal/src/lptimer.rs b/stm32l4xx-hal/src/lptimer.rs new file mode 100644 index 0000000..219a516 --- /dev/null +++ b/stm32l4xx-hal/src/lptimer.rs @@ -0,0 +1,316 @@ +//! Low power timers +use crate::rcc::{Clocks, Enable, RccBus, Reset, CCIPR}; + +use crate::stm32::{LPTIM1, LPTIM2, RCC}; + +/// Clock sources available for timers +pub enum ClockSource { + /// Use PCLK as clock source + PCLK = 0b00, + /// Use LSI as clock source + LSI = 0b01, + /// Use HSI16 as clock source + HSI16 = 0b10, + /// Use LSE as clock source + LSE = 0b11, +} + +/// The prescaler value to use for a timer +/// +/// Allow missing docs because the type is self explanatory +#[allow(missing_docs)] +pub enum PreScaler { + U1 = 0b000, + U2 = 0b001, + U4 = 0b010, + U8 = 0b011, + U16 = 0b100, + U32 = 0b101, + U64 = 0b110, + U128 = 0b111, +} + +/// Count modes that are available. +/// +/// All ClockSources currently supported require the Internal count mode +#[derive(PartialEq)] +pub enum CountMode { + /// Use an internal clock source (which also includes LSE) + Internal, + // External, +} + +/// All currently supported interrupt events +pub enum Event { + /// Occurs when the compare value is the same as the counter value + CompareMatch, + /// Occurs when the arr value is the same as the counter value. + /// When this event occurs, the counter value is set to 0 (by hardware) + AutoReloadMatch, +} + +/// Configuration of a low power timer +pub struct LowPowerTimerConfig { + clock_source: ClockSource, + prescaler: PreScaler, + count_mode: CountMode, + compare_value: u16, + arr_value: u16, +} + +impl Default for LowPowerTimerConfig { + fn default() -> Self { + Self { + clock_source: ClockSource::LSI, + prescaler: PreScaler::U1, + count_mode: CountMode::Internal, + compare_value: 0x0, + arr_value: 0xFFFF, + } + } +} + +impl LowPowerTimerConfig { + /// Select which clock source should be used + pub fn clock_source(mut self, clock_source: ClockSource) -> Self { + self.clock_source = clock_source; + self + } + + /// Select which prescaler value should be used + pub fn prescaler(mut self, prescaler: PreScaler) -> Self { + self.prescaler = prescaler; + self + } + + /// Select the count mode that should be used + pub fn count_mode(mut self, count_mode: CountMode) -> Self { + self.count_mode = count_mode; + self + } + + /// Set the value of the compare register + pub fn compare_value(mut self, compare_value: u16) -> Self { + self.compare_value = compare_value; + self + } + + /// Set the value of the auto reload register + pub fn arr_value(mut self, arr_value: u16) -> Self { + self.arr_value = arr_value; + self + } +} + +/// A low power hardware timer +/// +/// Supported things: +/// * Compare match +/// * Auto reload matches +pub struct LowPowerTimer { + lptim: LPTIM, +} + +macro_rules! hal { + ($timer_type: ident, $lptimX: ident, $timXsel: ident) => { + impl LowPowerTimer<$timer_type> { + #[inline(always)] + fn enable(&mut self) { + self.set_enable(true); + } + + #[inline(always)] + fn disable(&mut self) { + self.set_enable(false); + } + + #[inline(always)] + fn set_enable(&mut self, enabled: bool) { + self.lptim.cr.modify(|_, w| w.enable().bit(enabled)); + } + + #[inline(always)] + fn start_continuous_mode(&mut self) { + self.lptim.cr.modify(|_, w| w.cntstrt().set_bit()); + } + + /// Consume the LPTIM and produce a LowPowerTimer that encapsulates + /// said LPTIM. + /// + /// `config` contains details about the desired configuration for the + /// LowPowerTimer + /// + /// # Panics + /// This function panics if the value of ARR is less than or equal to CMP, + /// and if the clock source is HSI16, LSI, or LSE and that clock is not enabled. + pub fn $lptimX( + lptim: $timer_type, + config: LowPowerTimerConfig, + apb1rn: &mut <$timer_type as RccBus>::Bus, + ccipr: &mut CCIPR, + clocks: Clocks, + ) -> Self { + let LowPowerTimerConfig { + clock_source, + count_mode, + prescaler, + compare_value, + arr_value, + } = config; + + // ARR value must be strictly greater than CMP value + assert!(arr_value > compare_value); + + // The used clock source must actually be enabled + // PCLK is always on if a `Clocks` eixsts. + match clock_source { + ClockSource::LSE => assert!(clocks.lse()), + ClockSource::LSI => assert!(clocks.lsi()), + // Check if HSI16 is enabled + // This operation is sound, as it is an atomic memory access + // that does not modify the memory/read value + ClockSource::HSI16 => { + assert!(unsafe { (&*RCC::ptr()).cr.read().hsion().bit_is_set() }) + } + _ => {} + } + + <$timer_type>::enable(apb1rn); + <$timer_type>::reset(apb1rn); + + // This operation is sound as `ClockSource as u8` only produces valid values + ccipr + .ccipr() + .modify(|_, w| unsafe { w.$timXsel().bits(clock_source as u8) }); + + // This operation is sound as `PreScaler as u8` (which is the "unsafe" part) only + // produces valid values + lptim.cfgr.modify(|_, w| unsafe { + w.enc() + .clear_bit() + .countmode() + .bit(count_mode != CountMode::Internal) + .presc() + .bits(prescaler as u8) + .cksel() + .clear_bit() + }); + + let mut instance = LowPowerTimer { lptim }; + + instance.enable(); + + instance.start_continuous_mode(); + instance.set_autoreload(arr_value); + instance.set_compare_match(compare_value); + instance + } + + /// Enable interrupts for the specified event + pub fn listen(&mut self, event: Event) { + // LPTIM_IER may only be modified when LPTIM is disabled + self.disable(); + self.lptim.ier.modify(|_, w| match event { + Event::CompareMatch => w.cmpmie().set_bit(), + Event::AutoReloadMatch => w.arrmie().set_bit(), + }); + self.enable(); + self.start_continuous_mode(); + } + + /// Disable interrupts for the specified event + pub fn unlisten(&mut self, event: Event) { + // LPTIM_IER may only be modified when LPTIM is disabled + self.disable(); + self.lptim.ier.modify(|_, w| match event { + Event::CompareMatch => w.cmpmie().clear_bit(), + Event::AutoReloadMatch => w.arrmie().clear_bit(), + }); + self.enable(); + self.start_continuous_mode(); + } + + /// Check if the specified event has been triggered for this LowPowerTimer. + /// + /// If this function returns `true` for an `Event` that this LowPowerTimer is listening for, + /// [`LowPowerTimer::clear_event_flag`] must be called for that event to prevent the + /// interrupt from looping eternally. This is not done in a single function to + /// avoid using a mutable reference for an operation that does not require it. + pub fn is_event_triggered(&self, event: Event) -> bool { + let reg_val = self.lptim.isr.read(); + match event { + Event::CompareMatch => reg_val.cmpm().bit_is_set(), + Event::AutoReloadMatch => reg_val.arrm().bit_is_set(), + } + } + + /// Clear the interrupt flag for the specified event + pub fn clear_event_flag(&mut self, event: Event) { + self.lptim.icr.write(|w| match event { + Event::CompareMatch => w.cmpmcf().set_bit(), + Event::AutoReloadMatch => w.arrmcf().set_bit(), + }); + } + + /// Set the compare match field for this LowPowerTimer + #[inline] + pub fn set_compare_match(&mut self, value: u16) { + // clear compare register update ok flag + self.lptim.icr.write(|w| w.cmpokcf().set_bit()); + + // This operation is sound as compare_value is a u16, and there are 16 writeable bits + // Additionally, the LPTIM peripheral will always be in the enabled state when this code is called + self.lptim.cmp.write(|w| unsafe { w.bits(value as u32) }); + + // wait for compare register update ok interrupt to be signalled + // (see RM0394 Rev 4, sec 30.4.10 for further explanation and + // sec. 30.7.1, Bit 4 for register field description) + while self.lptim.isr.read().cmpok().bit_is_clear() {} + } + + /// Set auto reload register + /// has to be used _after_ enabling of lptim + #[inline(always)] + pub fn set_autoreload(&mut self, arr_value: u16) { + // clear autoreload register OK interrupt flag + self.lptim.icr.write(|w| w.arrokcf().set_bit()); + + // Write autoreload value + // This operation is sound as arr_value is a u16, and there are 16 writeable bits + self.lptim + .arr + .write(|w| unsafe { w.bits(arr_value as u32) }); + + // wait for autoreload write ok interrupt to be signalled + // (see RM0394 Rev 4, sec 30.4.10 for further explanation and + // sec. 30.7.1, Bit 4 for register field description) + while self.lptim.isr.read().arrok().bit_is_clear() {} + } + + /// Get the current counter value for this LowPowerTimer + #[inline] + pub fn get_counter(&self) -> u16 { + self.lptim.cnt.read().bits() as u16 + } + + /// Get the value of the ARR register for this + /// LowPowerTimer + #[inline] + pub fn get_arr(&self) -> u16 { + self.lptim.arr.read().bits() as u16 + } + + pub fn pause(&mut self) { + self.disable(); + } + + pub fn resume(&mut self) { + self.enable(); + self.start_continuous_mode(); + } + } + }; +} + +hal!(LPTIM1, lptim1, lptim1sel); +hal!(LPTIM2, lptim2, lptim2sel); diff --git a/stm32l4xx-hal/src/otg_fs.rs b/stm32l4xx-hal/src/otg_fs.rs new file mode 100644 index 0000000..8e0f88f --- /dev/null +++ b/stm32l4xx-hal/src/otg_fs.rs @@ -0,0 +1,52 @@ +//! USB OTG full-speed peripheral +//! +//! The STM32L4 series only supports the full-speed peripheral. + +use crate::rcc::{Enable, Reset}; +use crate::stm32; + +use crate::gpio::{ + gpioa::{PA11, PA12}, + Alternate, PushPull, +}; +use crate::time::Hertz; + +pub use synopsys_usb_otg::UsbBus; +use synopsys_usb_otg::UsbPeripheral; + +pub struct USB { + pub usb_global: stm32::OTG_FS_GLOBAL, + pub usb_device: stm32::OTG_FS_DEVICE, + pub usb_pwrclk: stm32::OTG_FS_PWRCLK, + // TODO: check type + pub pin_dm: PA11>, + pub pin_dp: PA12>, + pub hclk: Hertz, +} + +unsafe impl Sync for USB {} + +unsafe impl UsbPeripheral for USB { + const REGISTERS: *const () = stm32::OTG_FS_GLOBAL::ptr() as *const (); + + const HIGH_SPEED: bool = false; + const FIFO_DEPTH_WORDS: usize = 320; + + const ENDPOINT_COUNT: usize = 6; + + fn enable() { + cortex_m::interrupt::free(|_| unsafe { + // Enable USB peripheral + stm32::OTG_FS_GLOBAL::enable_unchecked(); + + // Reset USB peripheral + stm32::OTG_FS_GLOBAL::reset_unchecked(); + }); + } + + fn ahb_frequency_hz(&self) -> u32 { + self.hclk.to_Hz() + } +} + +pub type UsbBusType = UsbBus; diff --git a/stm32l4xx-hal/src/prelude.rs b/stm32l4xx-hal/src/prelude.rs new file mode 100644 index 0000000..c980fd0 --- /dev/null +++ b/stm32l4xx-hal/src/prelude.rs @@ -0,0 +1,18 @@ +//! Prelude - Include traits for hal + +pub use crate::hal::digital::v2::*; +pub use crate::hal::prelude::*; // embedded hal traits // for some reason v2 is not exported in the ehal prelude + +pub use crate::crc::CrcExt as _stm32l4_hal_CrcExt; +pub use crate::datetime::U32Ext as _stm32l4_hal_datetime_U32Ext; +pub use crate::dma::DmaExt as _stm32l4_hal_DmaExt; +pub use crate::flash::FlashExt as _stm32l4_hal_FlashExt; +pub use crate::gpio::ExtiPin as _stm32l4_hal_ExtiPin; +pub use crate::gpio::GpioExt as _stm32l4_hal_GpioExt; +pub use crate::pwm::PwmExt1 as _stm32l4_hal_PwmExt1; +pub use crate::pwm::PwmExt2 as _stm32l4_hal_PwmExt2; +pub use crate::pwr::PwrExt as _stm32l4_hal_PwrExt; +pub use crate::rcc::RccExt as _stm32l4_hal_RccExt; +pub use crate::rng::RngExt as _stm32l4_hal_RngExt; +pub use crate::time::U32Ext as _stm32l4_hal_time_U32Ext; +pub use fugit::{ExtU32 as _, RateExtU32 as _}; diff --git a/stm32l4xx-hal/src/pwm.rs b/stm32l4xx-hal/src/pwm.rs new file mode 100644 index 0000000..2c371bb --- /dev/null +++ b/stm32l4xx-hal/src/pwm.rs @@ -0,0 +1,400 @@ +//! # Pulse Width Modulation + +use core::marker::PhantomData; +use core::mem; + +use crate::hal; +use crate::stm32::{TIM1, TIM15, TIM2}; + +use crate::gpio::gpioa::{PA0, PA1, PA10, PA11, PA15, PA2, PA3, PA8, PA9}; +use crate::gpio::gpiob::{PB10, PB11, PB14, PB3}; +use crate::gpio::Alternate; +use crate::rcc::{Clocks, Enable, Reset, APB1R1, APB2}; +use crate::time::Hertz; + +// NB: REMAP is not implemented! +pub trait Pins { + // const REMAP: u8; + const C1: bool = false; + const C2: bool = false; + const C3: bool = false; + const C4: bool = false; + type Channels; +} + +macro_rules! pins_to_channels_mapping { + ( $( $TIMX:ident: ( $($PINX:ident),+ ), ( $($ENCHX:ident),+ ), ( $($AF:literal),+ ); )+ ) => { + $( + #[allow(unused_parens)] + impl Pins<$TIMX> for ($($PINX>),+) + { + $(const $ENCHX: bool = true;)+ + type Channels = ($(Pwm<$TIMX, $ENCHX>),+); + } + )+ + }; +} + +pins_to_channels_mapping! { + // TIM1 + TIM1: (PA8, PA9, PA10, PA11), (C1, C2, C3, C4), (1, 1, 1, 1); + TIM1: (PA9, PA10, PA11), (C2, C3, C4), (1, 1, 1); + TIM1: (PA8, PA10, PA11), (C1, C3, C4), (1, 1, 1); + TIM1: (PA8, PA9, PA11), (C1, C2, C4), (1, 1, 1); + TIM1: (PA8, PA9, PA10), (C1, C2, C3), (1, 1, 1); + TIM1: (PA10, PA11), (C3, C4), (1, 1); + TIM1: (PA9, PA11), (C2, C4), (1, 1); + TIM1: (PA9, PA10), (C2, C3), (1, 1); + TIM1: (PA8, PA11), (C1, C4), (1, 1); + TIM1: (PA8, PA10), (C1, C3), (1, 1); + TIM1: (PA8, PA9), (C1, C2), (1, 1); + TIM1: (PA8), (C1), (1); + TIM1: (PA9), (C2), (1); + TIM1: (PA10), (C3), (1); + TIM1: (PA11), (C4), (1); + + // TIM2 + TIM2: (PA0, PA1, PA2, PA3), (C1, C2, C3, C4), (1, 1, 1, 1); + TIM2: (PA0, PA1, PA2, PB11), (C1, C2, C3, C4), (1, 1, 1, 1); + TIM2: (PA15, PB3, PB10, PB11), (C1, C2, C3, C4), (1, 1, 1, 1); + + TIM2: (PA1, PA2, PA3), (C2, C3, C4), (1, 1, 1); + TIM2: (PA0, PA2, PA3), (C1, C3, C4), (1, 1, 1); + TIM2: (PA0, PA1, PA3), (C1, C2, C4), (1, 1, 1); + TIM2: (PA0, PA1, PA2), (C1, C2, C3), (1, 1, 1); + + TIM2: (PB3, PB10, PB11), (C2, C3, C4), (1, 1, 1); + TIM2: (PA15, PB10, PB11), (C1, C3, C4), (1, 1, 1); + TIM2: (PA15, PB3, PB11), (C1, C2, C4), (1, 1, 1); + TIM2: (PA15, PB3, PB10), (C1, C2, C3), (1, 1, 1); + + TIM2: (PA2, PA3), (C3, C4), (1, 1); + TIM2: (PA1, PA3), (C2, C4), (1, 1); + TIM2: (PA1, PA2), (C2, C3), (1, 1); + TIM2: (PA0, PA3), (C1, C4), (1, 1); + TIM2: (PA0, PA2), (C1, C3), (1, 1); + TIM2: (PA0, PA1), (C1, C2), (1, 1); + + TIM2: (PB10, PB11), (C3, C4), (1, 1); + TIM2: (PB3, PB11), (C2, C4), (1, 1); + TIM2: (PB3, PB10), (C2, C3), (1, 1); + TIM2: (PA15, PB11), (C1, C4), (1, 1); + TIM2: (PA15, PB10), (C1, C3), (1, 1); + TIM2: (PA15, PB3), (C1, C2), (1, 1); + + TIM2: (PA0), (C1), (1); + TIM2: (PA1), (C2), (1); + TIM2: (PA2), (C3), (1); + TIM2: (PA3), (C4), (1); + + TIM2: (PA15), (C1), (1); + TIM2: (PB3), (C2), (1); + TIM2: (PB10), (C3), (1); + TIM2: (PB11), (C4), (1); + + // TIM15 - TODO: The uncommented lines are awaiting PAC updates to be valid. + TIM15: (PB14), (C1), (14); + // TIM15: (PB15), (C2), (14); + TIM15: (PA2), (C1), (14); + // TIM15: (PA3), (C2), (14); + // TIM15: (PB14, PB15), (C1, C2), (14, 14); + // TIM15: (PB14, PA3), (C1, C2), (14, 14); + // TIM15: (PA2, PB15), (C1, C2), (14, 14); + // TIM15: (PA2, PA3), (C1, C2), (14, 14); +} + +pub trait PwmExt1: Sized { + fn pwm(self, _: PINS, frequency: Hertz, clocks: Clocks, apb: &mut APB2) -> PINS::Channels + where + PINS: Pins; +} + +pub trait PwmExt2: Sized { + fn pwm( + self, + _: PINS, + frequency: Hertz, + clocks: Clocks, + apb: &mut APB1R1, + ) -> PINS::Channels + where + PINS: Pins; +} + +impl PwmExt1 for TIM1 { + fn pwm(self, _pins: PINS, freq: Hertz, clocks: Clocks, apb: &mut APB2) -> PINS::Channels + where + PINS: Pins, + { + tim1(self, _pins, freq, clocks, apb) + } +} + +impl PwmExt1 for TIM15 { + fn pwm(self, _pins: PINS, freq: Hertz, clocks: Clocks, apb: &mut APB2) -> PINS::Channels + where + PINS: Pins, + { + tim15(self, _pins, freq, clocks, apb) + } +} + +impl PwmExt2 for TIM2 { + fn pwm(self, _pins: PINS, freq: Hertz, clocks: Clocks, apb: &mut APB1R1) -> PINS::Channels + where + PINS: Pins, + { + // TODO: check if this is really not needed (in the f1xx examples value + // of remap is 0x0). if so, what's afio.mapr on l4xx? + // + // mapr.mapr() + // .modify(|_, w| unsafe { w.tim2_remap().bits(PINS::REMAP) }); + + tim2(self, _pins, freq, clocks, apb) + } +} + +pub struct Pwm { + _channel: PhantomData, + _tim: PhantomData, +} + +pub struct C1; +pub struct C2; +pub struct C3; +pub struct C4; + +macro_rules! advanced_timer { + ($($TIMX:ident: ($timX:ident, $apb:ident, $psc_width:ident, $arr_width:ident),)+) => { + $( + fn $timX( + tim: $TIMX, + _pins: PINS, + freq: Hertz, + clocks: Clocks, + apb: &mut $apb, + ) -> PINS::Channels + where + PINS: Pins<$TIMX>, + { + <$TIMX>::enable(apb); + <$TIMX>::reset(apb); + + if PINS::C1 { + tim.ccmr1_output().modify(|_, w| w.oc1pe().set_bit().oc1m().bits(6)); + } + + if PINS::C2 { + tim.ccmr1_output().modify(|_, w| w.oc2pe().set_bit().oc2m().bits(6)); + } + + if PINS::C3 { + tim.ccmr2_output().modify(|_, w| w.oc3pe().set_bit().oc3m().bits(6)); + } + + if PINS::C4 { + tim.ccmr2_output().modify(|_, w| w.oc4pe().set_bit().oc4m().bits(6)); + } + + let clk = clocks.pclk2(); + let ticks = clk / freq; + + // maybe this is all u32? also, why no `- 1` vs `timer.rs`? + let psc = ticks / (1 << 16); + tim.psc.write(|w| { w.psc().bits(psc as $psc_width) }); + let arr = ticks / (psc + 1); + tim.arr.write(|w| { w.arr().bits(arr as $arr_width) }); + + // Only for the advanced control timer + tim.bdtr.write(|w| w.moe().set_bit()); + tim.egr.write(|w| w.ug().set_bit()); + + tim.cr1.write(|w| { + w.cms() + .bits(0b00) + .dir().clear_bit() + .opm().clear_bit() + .cen().set_bit() + .arpe().set_bit() + }); + + unsafe { mem::MaybeUninit::uninit().assume_init() } + } + + pwm_channels! { + $TIMX: (C1, $arr_width, cc1e, ccr1, ccr), + (C2, $arr_width, cc2e, ccr2, ccr), + (C3, $arr_width, cc3e, ccr3, ccr), + (C4, $arr_width, cc4e, ccr4, ccr), + } + + )+ + } +} + +macro_rules! standard_timer { + ($($TIMX:ident: ($timX:ident, $apb:ident, $psc_width:ident, $arr_width:ident),)+) => { + $( + fn $timX( + tim: $TIMX, + _pins: PINS, + freq: Hertz, + clocks: Clocks, + apb: &mut $apb, + ) -> PINS::Channels + where + PINS: Pins<$TIMX>, + { + <$TIMX>::enable(apb); + <$TIMX>::reset(apb); + + if PINS::C1 { + tim.ccmr1_output().modify(|_, w| w.oc1pe().set_bit().oc1m().bits(6)); + } + + if PINS::C2 { + tim.ccmr1_output().modify(|_, w| w.oc2pe().set_bit().oc2m().bits(6)); + } + + if PINS::C3 { + tim.ccmr2_output().modify(|_, w| w.oc3pe().set_bit().oc3m().bits(6)); + } + + if PINS::C4 { + tim.ccmr2_output().modify(|_, w| w.oc4pe().set_bit().oc4m().bits(6)); + } + + let clk = clocks.pclk1(); + let ticks = clk / freq; + + // maybe this is all u32? also, why no `- 1` vs `timer.rs`? + let psc = ticks / (1 << 16); + tim.psc.write(|w| { w.psc().bits(psc as $psc_width) }); + let arr = ticks / (psc + 1); + tim.arr.write(|w| { w.arr().bits(arr as $arr_width) }); + + tim.cr1.write(|w| { + w.cms() + .bits(0b00) + .dir().clear_bit() + .opm().clear_bit() + .cen().set_bit() + .arpe().set_bit() + }); + + unsafe { mem::MaybeUninit::uninit().assume_init() } + } + + pwm_channels! { + $TIMX: (C1, $arr_width, cc1e, ccr1, ccr), + (C2, $arr_width, cc2e, ccr2, ccr), + (C3, $arr_width, cc3e, ccr3, ccr), + (C4, $arr_width, cc4e, ccr4, ccr), + } + + )+ + } +} + +macro_rules! small_timer { + ($($TIMX:ident: ($timX:ident, $apb:ident, $psc_width:ident, $arr_width:ident),)+) => { + $( + fn $timX( + tim: $TIMX, + _pins: PINS, + freq: Hertz, + clocks: Clocks, + apb: &mut $apb, + ) -> PINS::Channels + where + PINS: Pins<$TIMX>, + { + <$TIMX>::enable(apb); + <$TIMX>::reset(apb); + + if PINS::C1 { + tim.ccmr1_output().modify(|_, w| w.oc1pe().set_bit().oc1m().bits(6)); + } + + // TODO: The uncommented lines are awaiting PAC updates to be valid. + // if PINS::C2 { + // tim.ccmr1_output().modify(|_, w| w.oc2pe().set_bit().oc2m().bits(6)); + // } + + let clk = clocks.pclk1(); + let ticks = clk / freq; + + // maybe this is all u32? also, why no `- 1` vs `timer.rs`? + let psc = ticks / (1 << 16); + tim.psc.write(|w| { w.psc().bits(psc as $psc_width) }); + let arr = ticks / (psc + 1); + unsafe { tim.arr.write(|w| { w.arr().bits(arr as $arr_width) }); } + + tim.bdtr.write(|w| w.moe().set_bit()); + tim.egr.write(|w| w.ug().set_bit()); + + tim.cr1.write(|w| { + w.opm().clear_bit() + .cen().set_bit() + .arpe().set_bit() + }); + + unsafe { mem::MaybeUninit::uninit().assume_init() } + } + + pwm_channels! { + $TIMX: (C1, $arr_width, cc1e, ccr1, ccr), + // TODO: The uncommented line is awaiting PAC updates to be valid. + // (C2, $arr_width, cc2e, ccr2, ccr2), + } + + )+ + } +} + +macro_rules! pwm_channels { + ($TIMX:ident: $(($channel:ident, $arr_width:ident, $ccXe:ident, $ccrX:ident, $ccr:ident),)+) => { + $( + impl hal::PwmPin for Pwm<$TIMX, $channel> { + type Duty = $arr_width; + + #[inline(always)] + fn disable(&mut self) { + unsafe { (*$TIMX::ptr()).ccer.modify(|_, w| w.$ccXe().clear_bit()) } + } + + #[inline(always)] + fn enable(&mut self) { + unsafe { (*$TIMX::ptr()).ccer.modify(|_, w| w.$ccXe().set_bit()) } + } + + #[inline(always)] + fn get_duty(&self) -> Self::Duty { + unsafe { (*$TIMX::ptr()).$ccrX.read().$ccr().bits() } + } + + #[inline(always)] + fn get_max_duty(&self) -> Self::Duty { + unsafe { (*$TIMX::ptr()).arr.read().arr().bits() } + } + + #[inline(always)] + fn set_duty(&mut self, duty: Self::Duty) { + unsafe { (*$TIMX::ptr()).$ccrX.write(|w| w.$ccr().bits(duty)) } + } + } + )+ + } +} + +advanced_timer! { + TIM1: (tim1, APB2, u16, u16), +} + +standard_timer! { + TIM2: (tim2, APB1R1, u16, u32), +} + +small_timer! { + TIM15: (tim15, APB2, u16, u16), +} diff --git a/stm32l4xx-hal/src/pwr.rs b/stm32l4xx-hal/src/pwr.rs new file mode 100644 index 0000000..3196056 --- /dev/null +++ b/stm32l4xx-hal/src/pwr.rs @@ -0,0 +1,83 @@ +//! Power management + +use crate::rcc::{Enable, APB1R1}; +use crate::stm32::{pwr, PWR}; + +pub struct Pwr { + pub cr1: CR1, + pub cr2: CR2, + pub cr3: CR3, + pub cr4: CR4, +} + +/// Extension trait that constrains the `PWR` peripheral +pub trait PwrExt { + /// Constrains the `PWR` peripheral so it plays nicely with the other abstractions + fn constrain(self, _: &mut APB1R1) -> Pwr; +} + +impl PwrExt for PWR { + fn constrain(self, apb1r1: &mut APB1R1) -> Pwr { + // Enable the peripheral clock + PWR::enable(apb1r1); + Pwr { + cr1: CR1 { _0: () }, + cr2: CR2 { _0: () }, + cr3: CR3 { _0: () }, + cr4: CR4 { _0: () }, + } + } +} + +/// CR1 +pub struct CR1 { + _0: (), +} + +impl CR1 { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn reg(&mut self) -> &pwr::CR1 { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*PWR::ptr()).cr1 } + } +} +/// CR2 +pub struct CR2 { + _0: (), +} + +impl CR2 { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn reg(&mut self) -> &pwr::CR2 { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*PWR::ptr()).cr2 } + } +} +/// CR3 +pub struct CR3 { + _0: (), +} + +impl CR3 { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn reg(&mut self) -> &pwr::CR3 { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*PWR::ptr()).cr3 } + } +} +/// CR4 +pub struct CR4 { + _0: (), +} + +impl CR4 { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn reg(&mut self) -> &pwr::CR4 { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*PWR::ptr()).cr4 } + } +} diff --git a/stm32l4xx-hal/src/qspi.rs b/stm32l4xx-hal/src/qspi.rs new file mode 100644 index 0000000..5a6bea0 --- /dev/null +++ b/stm32l4xx-hal/src/qspi.rs @@ -0,0 +1,738 @@ +//! Quad Serial Peripheral Interface (QSPI) bus + +use crate::gpio::{ + gpioa::{PA6, PA7}, + gpiob::{PB0, PB1, PB10, PB11}, + gpioe::{PE10, PE11, PE12, PE13, PE14, PE15}, +}; + +#[cfg(not(any(feature = "stm32l475")))] +use crate::gpio::{ + gpioa::{PA2, PA3}, + gpiod::{PD3, PD4, PD5, PD6, PD7}, +}; + +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" +))] +use crate::gpio::{ + gpioc::{PC1, PC2, PC4, PC5}, + gpiof::{PF6, PF7, PF8, PF9}, +}; + +use crate::gpio::{Alternate, PushPull, Speed}; +use crate::rcc::{Enable, AHB3}; +use crate::stm32::QUADSPI; +use core::ptr; + +#[doc(hidden)] +mod private { + pub trait Sealed {} +} + +/// CLK pin. This trait is sealed and cannot be implemented. +pub trait ClkPin: private::Sealed { + fn set_speed(self, speed: Speed) -> Self; +} +/// nCS pin. This trait is sealed and cannot be implemented. +pub trait NCSPin: private::Sealed { + fn set_speed(self, speed: Speed) -> Self; +} +/// IO0 pin. This trait is sealed and cannot be implemented. +pub trait IO0Pin: private::Sealed { + fn set_speed(self, speed: Speed) -> Self; +} +/// IO1 pin. This trait is sealed and cannot be implemented. +pub trait IO1Pin: private::Sealed { + fn set_speed(self, speed: Speed) -> Self; +} +/// IO2 pin. This trait is sealed and cannot be implemented. +pub trait IO2Pin: private::Sealed { + fn set_speed(self, speed: Speed) -> Self; +} +/// IO3 pin. This trait is sealed and cannot be implemented. +pub trait IO3Pin: private::Sealed { + fn set_speed(self, speed: Speed) -> Self; +} + +macro_rules! pins { + ($qspi:ident, $af:literal, CLK: [$($clk:ident),*], nCS: [$($ncs:ident),*], + IO0: [$($io0:ident),*], IO1: [$($io1:ident),*], IO2: [$($io2:ident),*], + IO3: [$($io3:ident),*]) => { + $( + impl private::Sealed for $clk> {} + impl ClkPin<$qspi> for $clk> { + fn set_speed(self, speed: Speed) -> Self{ + self.set_speed(speed) + } + } + )* + $( + impl private::Sealed for $ncs> {} + impl NCSPin<$qspi> for $ncs> { + fn set_speed(self, speed: Speed) -> Self{ + self.set_speed(speed) + } + } + )* + $( + impl private::Sealed for $io0> {} + impl IO0Pin<$qspi> for $io0> { + fn set_speed(self, speed: Speed) -> Self{ + self.set_speed(speed) + } + } + )* + $( + impl private::Sealed for $io1> {} + impl IO1Pin<$qspi> for $io1> { + fn set_speed(self, speed: Speed) -> Self{ + self.set_speed(speed) + } + } + )* + $( + impl private::Sealed for $io2> {} + impl IO2Pin<$qspi> for $io2> { + fn set_speed(self, speed: Speed) -> Self{ + self.set_speed(speed) + } + } + )* + $( + impl private::Sealed for $io3> {} + impl IO3Pin<$qspi> for $io3> { + fn set_speed(self, speed: Speed) -> Self{ + self.set_speed(speed) + } + } + )* + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum QspiMode { + SingleChannel = 0b01, + DualChannel = 0b10, + QuadChannel = 0b11, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum AddressSize { + Addr8Bit = 0b00, + Addr16Bit = 0b01, + Addr24Bit = 0b10, + Addr32Bit = 0b11, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SampleShift { + None, + HalfACycle, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ClockMode { + Mode0, + Mode3, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum QspiError { + Busy, + Address, + Unknown, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct QspiConfig { + /// This field defines the scaler factor for generating CLK based on the AHB clock + /// (value+1). + clock_prescaler: u8, + /// Number of bytes in Flash memory = 2^[FSIZE+1] + flash_size: u8, + address_size: AddressSize, + /// This bit indicates the level that CLK takes between commands Mode 0(low) / mode 3(high) + clock_mode: ClockMode, + /// FIFO threshold level (Activates FTF, QUADSPI_SR[2]) 0-15. + fifo_threshold: u8, + sample_shift: SampleShift, + /// CSHT+1 defines the minimum number of CLK cycles which the chip select (nCS) must + /// remain high between commands issued to the Flash memory. + chip_select_high_time: u8, + qpi_mode: bool, +} + +impl Default for QspiConfig { + fn default() -> QspiConfig { + QspiConfig { + clock_prescaler: 0, + flash_size: 22, // 8MB // 26 = 128MB + address_size: AddressSize::Addr24Bit, + clock_mode: ClockMode::Mode0, + fifo_threshold: 1, + sample_shift: SampleShift::HalfACycle, + chip_select_high_time: 1, + qpi_mode: false, + } + } +} + +impl QspiConfig { + pub fn clock_prescaler(mut self, clk_pre: u8) -> Self { + self.clock_prescaler = clk_pre; + self + } + + pub fn flash_size(mut self, fl_size: u8) -> Self { + self.flash_size = fl_size; + self + } + + pub fn address_size(mut self, add_size: AddressSize) -> Self { + self.address_size = add_size; + self + } + + pub fn clock_mode(mut self, clk_mode: ClockMode) -> Self { + self.clock_mode = clk_mode; + self + } + + pub fn fifo_threshold(mut self, fifo_thres: u8) -> Self { + self.fifo_threshold = fifo_thres; + self + } + + pub fn sample_shift(mut self, shift: SampleShift) -> Self { + self.sample_shift = shift; + self + } + + pub fn chip_select_high_time(mut self, csht: u8) -> Self { + self.chip_select_high_time = csht; + self + } + + pub fn qpi_mode(mut self, qpi: bool) -> Self { + self.qpi_mode = qpi; + self + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct QspiWriteCommand<'a> { + pub instruction: Option<(u8, QspiMode)>, + pub address: Option<(u32, QspiMode)>, + pub alternative_bytes: Option<(&'a [u8], QspiMode)>, + pub dummy_cycles: u8, + pub data: Option<(&'a [u8], QspiMode)>, + pub double_data_rate: bool, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct QspiReadCommand<'a> { + pub instruction: Option<(u8, QspiMode)>, + pub address: Option<(u32, QspiMode)>, + pub alternative_bytes: Option<(&'a [u8], QspiMode)>, + pub dummy_cycles: u8, + pub data_mode: QspiMode, + pub receive_length: u32, + pub double_data_rate: bool, +} + +impl<'a> QspiWriteCommand<'a> { + pub fn address(self, addr: u32, mode: QspiMode) -> Self { + QspiWriteCommand { + address: Some((addr, mode)), + ..self + } + } + + pub fn alternative_bytes(self, bytes: &'a [u8], mode: QspiMode) -> Self { + QspiWriteCommand { + alternative_bytes: Some((bytes, mode)), + ..self + } + } + + pub fn dummy_cycles(self, n: u8) -> Self { + QspiWriteCommand { + dummy_cycles: n, + ..self + } + } + + pub fn data(self, bytes: &'a [u8], mode: QspiMode) -> Self { + QspiWriteCommand { + data: Some((bytes, mode)), + ..self + } + } +} + +impl<'a> QspiReadCommand<'a> { + pub fn address(self, addr: u32, mode: QspiMode) -> Self { + QspiReadCommand { + address: Some((addr, mode)), + ..self + } + } + + pub fn alternative_bytes(self, bytes: &'a [u8], mode: QspiMode) -> Self { + QspiReadCommand { + alternative_bytes: Some((bytes, mode)), + ..self + } + } + + pub fn dummy_cycles(self, n: u8) -> Self { + QspiReadCommand { + dummy_cycles: n, + ..self + } + } + + pub fn receive_length(self, length: u32) -> Self { + QspiReadCommand { + receive_length: length, + ..self + } + } +} + +pub struct Qspi { + qspi: QUADSPI, + _pins: PINS, + config: QspiConfig, +} + +impl Qspi<(CLK, NCS, IO0, IO1, IO2, IO3)> { + pub fn new( + qspi: QUADSPI, + pins: (CLK, NCS, IO0, IO1, IO2, IO3), + ahb3: &mut AHB3, + config: QspiConfig, + ) -> Self + where + CLK: ClkPin, + NCS: NCSPin, + IO0: IO0Pin, + IO1: IO1Pin, + IO2: IO2Pin, + IO3: IO3Pin, + { + // Enable quad SPI in the clocks. + QUADSPI::enable(ahb3); + + // Disable QUADSPI before configuring it. + qspi.cr.modify(|_, w| w.en().clear_bit()); + + // Clear all pending flags. + qspi.fcr.write(|w| { + w.ctof() + .set_bit() + .csmf() + .set_bit() + .ctcf() + .set_bit() + .ctef() + .set_bit() + }); + + // Set gpio speed + let high_speed_pins = ( + pins.0.set_speed(Speed::VeryHigh), + pins.1.set_speed(Speed::VeryHigh), + pins.2.set_speed(Speed::VeryHigh), + pins.3.set_speed(Speed::VeryHigh), + pins.4.set_speed(Speed::VeryHigh), + pins.5.set_speed(Speed::VeryHigh), + ); + + let mut unit = Qspi { + qspi, + _pins: high_speed_pins, + config, + }; + unit.apply_config(config); + unit + } + + pub fn is_busy(&self) -> bool { + self.qspi.sr.read().busy().bit_is_set() + } + + /// Aborts any ongoing transaction + /// Note can cause problems if aborting writes to flash satus register + pub fn abort_transmission(&self) { + self.qspi.cr.modify(|_, w| w.abort().set_bit()); + while self.qspi.sr.read().busy().bit_is_set() {} + } + + pub fn get_config(&self) -> QspiConfig { + self.config + } + + pub fn apply_config(&mut self, config: QspiConfig) { + if self.qspi.sr.read().busy().bit_is_set() { + self.abort_transmission(); + } + + self.qspi + .cr + .modify(|_, w| unsafe { w.fthres().bits(config.fifo_threshold as u8) }); + + while self.qspi.sr.read().busy().bit_is_set() {} + + // Modify the prescaler and select flash bank 2 - flash bank 1 is currently unsupported. + self.qspi.cr.modify(|_, w| unsafe { + w.prescaler() + .bits(config.clock_prescaler as u8) + .sshift() + .bit(config.sample_shift == SampleShift::HalfACycle) + }); + while self.is_busy() {} + + // Modify DCR with flash size, CSHT and clock mode + self.qspi.dcr.modify(|_, w| unsafe { + w.fsize() + .bits(config.flash_size as u8) + .csht() + .bits(config.chip_select_high_time as u8) + .ckmode() + .bit(config.clock_mode == ClockMode::Mode3) + }); + while self.is_busy() {} + + // Enable QSPI + self.qspi.cr.modify(|_, w| w.en().set_bit()); + while self.is_busy() {} + + self.config = config; + } + + pub fn transfer(&self, command: QspiReadCommand, buffer: &mut [u8]) -> Result<(), QspiError> { + if self.is_busy() { + return Err(QspiError::Busy); + } + + // If double data rate change shift + if command.double_data_rate { + self.qspi.cr.modify(|_, w| w.sshift().bit(false)); + } + while self.is_busy() {} + + // Clear the transfer complete flag. + self.qspi.fcr.modify(|_, w| w.ctcf().set_bit()); + + let mut dmode: u8 = 0; + let mut instruction: u8 = 0; + let mut imode: u8 = 0; + let mut admode: u8 = 0; + let mut adsize: u8 = 0; + let mut abmode: u8 = 0; + let mut absize: u8 = 0; + + // Write the length and format of data + if command.receive_length > 0 { + self.qspi + .dlr + .write(|w| unsafe { w.dl().bits(command.receive_length as u32 - 1) }); + if self.config.qpi_mode { + dmode = QspiMode::QuadChannel as u8; + } else { + dmode = command.data_mode as u8; + } + } + + // Write instruction mode + if let Some((inst, mode)) = command.instruction { + if self.config.qpi_mode { + imode = QspiMode::QuadChannel as u8; + } else { + imode = mode as u8; + } + instruction = inst; + } + + // Note Address mode + if let Some((_, mode)) = command.address { + if self.config.qpi_mode { + admode = QspiMode::QuadChannel as u8; + } else { + admode = mode as u8; + } + adsize = self.config.address_size as u8; + } + + // Write Alternative bytes + if let Some((a_bytes, mode)) = command.alternative_bytes { + if self.config.qpi_mode { + abmode = QspiMode::QuadChannel as u8; + } else { + abmode = mode as u8; + } + + absize = a_bytes.len() as u8 - 1; + + self.qspi.abr.write(|w| { + let mut reg_byte: u32 = 0; + for (i, element) in a_bytes.iter().rev().enumerate() { + reg_byte |= (*element as u32) << (i * 8); + } + unsafe { w.alternate().bits(reg_byte) } + }); + } + + // Write CCR register with instruction etc. + self.qspi.ccr.modify(|_, w| unsafe { + w.fmode() + .bits(0b01) + .admode() + .bits(admode) + .adsize() + .bits(adsize) + .abmode() + .bits(abmode) + .absize() + .bits(absize) + .ddrm() + .bit(command.double_data_rate) + .dcyc() + .bits(command.dummy_cycles) + .dmode() + .bits(dmode) + .imode() + .bits(imode) + .instruction() + .bits(instruction) + }); + + // Write address, triggers send + if let Some((addr, _)) = command.address { + self.qspi.ar.write(|w| unsafe { w.address().bits(addr) }); + + // Transfer error + if self.qspi.sr.read().tef().bit_is_set() { + return Err(QspiError::Address); + } + } + + // Transfer error + if self.qspi.sr.read().tef().bit_is_set() { + return Err(QspiError::Unknown); + } + + // Read data from the buffer + let mut b = buffer.iter_mut(); + while self.qspi.sr.read().tcf().bit_is_clear() { + if self.qspi.sr.read().ftf().bit_is_set() { + if let Some(v) = b.next() { + unsafe { + *v = ptr::read_volatile(&self.qspi.dr as *const _ as *const u8); + } + } else { + // OVERFLOW + } + } + } + // When transfer complete, empty fifo buffer + while self.qspi.sr.read().flevel().bits() > 0 { + if let Some(v) = b.next() { + unsafe { + *v = ptr::read_volatile(&self.qspi.dr as *const _ as *const u8); + } + } else { + // OVERFLOW + } + } + // If double data rate set shift back to original and if busy abort. + if command.double_data_rate { + if self.is_busy() { + self.abort_transmission(); + } + self.qspi.cr.modify(|_, w| { + w.sshift() + .bit(self.config.sample_shift == SampleShift::HalfACycle) + }); + } + while self.is_busy() {} + self.qspi.fcr.write(|w| w.ctcf().set_bit()); + Ok(()) + } + + pub fn write(&self, command: QspiWriteCommand) -> Result<(), QspiError> { + if self.is_busy() { + return Err(QspiError::Busy); + } + // Clear the transfer complete flag. + self.qspi.fcr.modify(|_, w| w.ctcf().set_bit()); + + let mut dmode: u8 = 0; + let mut instruction: u8 = 0; + let mut imode: u8 = 0; + let mut admode: u8 = 0; + let mut adsize: u8 = 0; + let mut abmode: u8 = 0; + let mut absize: u8 = 0; + + // Write the length and format of data + if let Some((data, mode)) = command.data { + self.qspi + .dlr + .write(|w| unsafe { w.dl().bits(data.len() as u32 - 1) }); + if self.config.qpi_mode { + dmode = QspiMode::QuadChannel as u8; + } else { + dmode = mode as u8; + } + } + + // Write instruction mode + if let Some((inst, mode)) = command.instruction { + if self.config.qpi_mode { + imode = QspiMode::QuadChannel as u8; + } else { + imode = mode as u8; + } + instruction = inst; + } + + // Note Address mode + if let Some((_, mode)) = command.address { + if self.config.qpi_mode { + admode = QspiMode::QuadChannel as u8; + } else { + admode = mode as u8; + } + adsize = self.config.address_size as u8; + } + + // Write Alternative bytes + if let Some((a_bytes, mode)) = command.alternative_bytes { + if self.config.qpi_mode { + abmode = QspiMode::QuadChannel as u8; + } else { + abmode = mode as u8; + } + + absize = a_bytes.len() as u8 - 1; + + self.qspi.abr.write(|w| { + let mut reg_byte: u32 = 0; + for (i, element) in a_bytes.iter().rev().enumerate() { + reg_byte |= (*element as u32) << (i * 8); + } + unsafe { w.alternate().bits(reg_byte) } + }); + } + + if command.double_data_rate { + self.qspi.cr.modify(|_, w| w.sshift().bit(false)); + } + + // Write CCR register with instruction etc. + self.qspi.ccr.modify(|_, w| unsafe { + w.fmode() + .bits(0b00) + .admode() + .bits(admode) + .adsize() + .bits(adsize) + .abmode() + .bits(abmode) + .absize() + .bits(absize) + .ddrm() + .bit(command.double_data_rate) + .dcyc() + .bits(command.dummy_cycles) + .dmode() + .bits(dmode) + .imode() + .bits(imode) + .instruction() + .bits(instruction) + }); + + // Write address, triggers send + if let Some((addr, _)) = command.address { + self.qspi.ar.write(|w| unsafe { w.address().bits(addr) }); + } + + // Transfer error + if self.qspi.sr.read().tef().bit_is_set() { + return Err(QspiError::Unknown); + } + + // Write data to the FIFO + if let Some((data, _)) = command.data { + for byte in data { + while self.qspi.sr.read().ftf().bit_is_clear() {} + unsafe { + ptr::write_volatile(&self.qspi.dr as *const _ as *mut u8, *byte); + } + } + } + + while self.qspi.sr.read().tcf().bit_is_clear() {} + + self.qspi.fcr.write(|w| w.ctcf().set_bit()); + + if self.is_busy() {} + + if command.double_data_rate { + self.qspi.cr.modify(|_, w| { + w.sshift() + .bit(self.config.sample_shift == SampleShift::HalfACycle) + }); + } + Ok(()) + } +} + +pins!( + QUADSPI, + 10, + CLK: [PE10, PB10], + nCS: [PE11, PB11], + IO0: [PE12, PB1], + IO1: [PE13, PB0], + IO2: [PE14, PA7], + IO3: [PE15, PA6] +); + +#[cfg(not(any(feature = "stm32l475")))] +pins!( + QUADSPI, + 10, + CLK: [PA3], + nCS: [PA2, PD3], + IO0: [PD4], + IO1: [PD5], + IO2: [PD6], + IO3: [PD7] +); + +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" +))] +pins!( + QUADSPI, + 10, + CLK: [], + nCS: [], + IO0: [PC1, PF8], + IO1: [PC2, PF9], + IO2: [PC4, PF7], + IO3: [PC5, PF6] +); diff --git a/stm32l4xx-hal/src/rcc.rs b/stm32l4xx-hal/src/rcc.rs new file mode 100644 index 0000000..ff7c4e5 --- /dev/null +++ b/stm32l4xx-hal/src/rcc.rs @@ -0,0 +1,965 @@ +//! Reset and Clock Control + +use crate::stm32::{rcc, RCC}; +use cast::u32; + +use crate::flash::ACR; +use crate::pwr::Pwr; +use crate::time::Hertz; +use fugit::RateExtU32; + +mod enable; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MsiFreq { + #[doc = "range 0 around 100 kHz"] + RANGE100K = 0, + #[doc = "range 1 around 200 kHz"] + RANGE200K = 1, + #[doc = "range 2 around 400 kHz"] + RANGE400K = 2, + #[doc = "range 3 around 800 kHz"] + RANGE800K = 3, + #[doc = "range 4 around 1 MHz"] + RANGE1M = 4, + #[doc = "range 5 around 2 MHz"] + RANGE2M = 5, + #[doc = "range 6 around 4 MHz"] + RANGE4M = 6, + #[doc = "range 7 around 8 MHz"] + RANGE8M = 7, + #[doc = "range 8 around 16 MHz"] + RANGE16M = 8, + #[doc = "range 9 around 24 MHz"] + RANGE24M = 9, + #[doc = "range 10 around 32 MHz"] + RANGE32M = 10, + #[doc = "range 11 around 48 MHz"] + RANGE48M = 11, +} + +impl MsiFreq { + fn to_hertz(self) -> Hertz { + (match self { + Self::RANGE100K => 100_000, + Self::RANGE200K => 200_000, + Self::RANGE400K => 400_000, + Self::RANGE800K => 800_000, + Self::RANGE1M => 1_000_000, + Self::RANGE2M => 2_000_000, + Self::RANGE4M => 4_000_000, + Self::RANGE8M => 8_000_000, + Self::RANGE16M => 16_000_000, + Self::RANGE24M => 24_000_000, + Self::RANGE32M => 32_000_000, + Self::RANGE48M => 48_000_000, + }) + .Hz() + } +} + +/// Extension trait that constrains the `RCC` peripheral +pub trait RccExt { + /// Constrains the `RCC` peripheral so it plays nicely with the other abstractions + fn constrain(self) -> Rcc; +} + +impl RccExt for RCC { + fn constrain(self) -> Rcc { + Rcc { + ahb1: AHB1::new(), + ahb2: AHB2::new(), + ahb3: AHB3::new(), + apb1r1: APB1R1::new(), + apb1r2: APB1R2::new(), + apb2: APB2::new(), + bdcr: BDCR { _0: () }, + csr: CSR { _0: () }, + crrcr: CRRCR { _0: () }, + ccipr: CCIPR { _0: () }, + cfgr: CFGR { + hse: None, + lse: None, + msi: None, + hsi48: false, + lsi: false, + hclk: None, + pclk1: None, + pclk2: None, + sysclk: None, + pll_source: None, + pll_config: None, + }, + } + } +} + +/// Constrained RCC peripheral +pub struct Rcc { + /// AMBA High-performance Bus (AHB1) registers + pub ahb1: AHB1, + /// AMBA High-performance Bus (AHB2) registers + pub ahb2: AHB2, + /// AMBA High-performance Bus (AHB3) registers + pub ahb3: AHB3, + /// Advanced Peripheral Bus 1 (APB1) registers + pub apb1r1: APB1R1, + /// Advanced Peripheral Bus 1 (APB2) registers + pub apb1r2: APB1R2, + /// Advanced Peripheral Bus 2 (APB2) registers + pub apb2: APB2, + /// Clock configuration register + pub cfgr: CFGR, + /// Backup domain control register + pub bdcr: BDCR, + /// Control/Status Register + pub csr: CSR, + /// Clock recovery RC register + pub crrcr: CRRCR, + /// Peripherals independent clock configuration register + pub ccipr: CCIPR, +} + +/// CSR Control/Status Register +pub struct CSR { + _0: (), +} + +impl CSR { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn csr(&mut self) -> &rcc::CSR { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).csr } + } +} + +/// Clock recovery RC register +pub struct CRRCR { + _0: (), +} + +impl CRRCR { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn crrcr(&mut self) -> &rcc::CRRCR { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).crrcr } + } + + /// Checks if the 48 MHz HSI is enabled + pub fn is_hsi48_on(&mut self) -> bool { + self.crrcr().read().hsi48on().bit() + } + + /// Checks if the 48 MHz HSI is ready + pub fn is_hsi48_ready(&mut self) -> bool { + self.crrcr().read().hsi48rdy().bit() + } +} + +/// Peripherals independent clock configuration register +pub struct CCIPR { + _0: (), +} + +impl CCIPR { + #[allow(dead_code)] + pub(crate) fn ccipr(&mut self) -> &rcc::CCIPR { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).ccipr } + } +} + +/// BDCR Backup domain control register registers +pub struct BDCR { + _0: (), +} + +impl BDCR { + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn enr(&mut self) -> &rcc::BDCR { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).bdcr } + } +} + +macro_rules! bus_struct { + ($($busX:ident => ($EN:ident, $en:ident, $SMEN:ident, $smen:ident, $RST:ident, $rst:ident, $doc:literal),)+) => { + $( + #[doc = $doc] + pub struct $busX { + _0: (), + } + + impl $busX { + pub(crate) fn new() -> Self { + Self { _0: () } + } + + #[allow(unused)] + pub(crate) fn enr(&self) -> &rcc::$EN { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).$en } + } + + #[allow(unused)] + pub(crate) fn smenr(&self) -> &rcc::$SMEN { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).$smen } + } + + #[allow(unused)] + pub(crate) fn rstr(&self) -> &rcc::$RST { + // NOTE(unsafe) this proxy grants exclusive access to this register + unsafe { &(*RCC::ptr()).$rst } + } + } + )+ + }; +} + +bus_struct! { + AHB1 => (AHB1ENR, ahb1enr, AHB1SMENR, ahb1smenr, AHB1RSTR, ahb1rstr, "Advanced High-performance Bus 1 (AHB1) registers"), + AHB2 => (AHB2ENR, ahb2enr, AHB2SMENR, ahb2smenr, AHB2RSTR, ahb2rstr, "Advanced High-performance Bus 2 (AHB2) registers"), + AHB3 => (AHB3ENR, ahb3enr, AHB3SMENR, ahb3smenr, AHB3RSTR, ahb3rstr, "Advanced High-performance Bus 3 (AHB3) registers"), + APB1R1 => (APB1ENR1, apb1enr1, APB1SMENR1, apb1smenr1, APB1RSTR1, apb1rstr1, "Advanced Peripheral Bus 1 (APB1) registers"), + APB1R2 => (APB1ENR2, apb1enr2, APB1SMENR2, apb1smenr2, APB1RSTR2, apb1rstr2, "Advanced Peripheral Bus 1 (APB1) registers"), + APB2 => (APB2ENR, apb2enr, APB2SMENR, apb2smenr, APB2RSTR, apb2rstr, "Advanced Peripheral Bus 2 (APB2) registers"), +} + +/// Bus associated to peripheral +pub trait RccBus: crate::Sealed { + /// Bus type; + type Bus; +} + +/// Enable/disable peripheral +pub trait Enable: RccBus { + /// Enables peripheral + fn enable(bus: &mut Self::Bus); + + /// Disables peripheral + fn disable(bus: &mut Self::Bus); + + /// Check if peripheral enabled + fn is_enabled() -> bool; + + /// Check if peripheral disabled + fn is_disabled() -> bool; + + /// # Safety + /// + /// Enables peripheral. Takes access to RCC internally + unsafe fn enable_unchecked(); + + /// # Safety + /// + /// Disables peripheral. Takes access to RCC internally + unsafe fn disable_unchecked(); +} + +/// Enable/disable peripheral in sleep mode +pub trait SMEnable: RccBus { + /// Enables peripheral + fn enable_in_sleep_mode(bus: &mut Self::Bus); + + /// Disables peripheral + fn disable_in_sleep_mode(bus: &mut Self::Bus); + + /// Check if peripheral enabled + fn is_enabled_in_sleep_mode() -> bool; + + /// Check if peripheral disabled + fn is_disabled_in_sleep_mode() -> bool; + + /// # Safety + /// + /// Enables peripheral. Takes access to RCC internally + unsafe fn enable_in_sleep_mode_unchecked(); + + /// # Safety + /// + /// Disables peripheral. Takes access to RCC internally + unsafe fn disable_in_sleep_mode_unchecked(); +} + +/// Reset peripheral +pub trait Reset: RccBus { + /// Resets peripheral + fn reset(bus: &mut Self::Bus); + + /// # Safety + /// + /// Resets peripheral. Takes access to RCC internally + unsafe fn reset_unchecked(); +} + +#[derive(Debug, PartialEq)] +/// HSE Configuration +struct HseConfig { + /// Clock speed of HSE + speed: u32, + /// If the clock driving circuitry is bypassed i.e. using an oscillator, not a crystal or + /// resonator + bypass: CrystalBypass, + /// Clock Security System enable/disable + css: ClockSecuritySystem, +} + +#[derive(Debug, PartialEq)] +/// LSE Configuration +struct LseConfig { + /// If the clock driving circuitry is bypassed i.e. using an oscillator, not a crystal or + /// resonator + bypass: CrystalBypass, + /// Clock Security System enable/disable + css: ClockSecuritySystem, +} + +/// Crystal bypass selector +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CrystalBypass { + /// If the clock driving circuitry is bypassed i.e. using an oscillator + Enable, + /// If the clock driving circuitry is not bypassed i.e. using a crystal or resonator + Disable, +} + +/// Clock Security System (CSS) selector +/// +/// When this is enabled on HSE it will fire of the NMI interrupt on failure and for the LSE the +/// MCU will be woken if in Standby and then the LSECSS interrupt will fire. See datasheet on how +/// to recover for CSS failures. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ClockSecuritySystem { + /// Enable the clock security system to detect clock failures + Enable, + /// Leave the clock security system disabled + Disable, +} + +const HSI: u32 = 16_000_000; // Hz + +/// Clock configuration +pub struct CFGR { + hse: Option, + lse: Option, + msi: Option, + hsi48: bool, + lsi: bool, + hclk: Option, + pclk1: Option, + pclk2: Option, + sysclk: Option, + pll_source: Option, + pll_config: Option, +} + +impl CFGR { + /// Add an HSE to the system + pub fn hse(mut self, freq: Hertz, bypass: CrystalBypass, css: ClockSecuritySystem) -> Self { + self.hse = Some(HseConfig { + speed: freq.raw(), + bypass, + css, + }); + + self + } + + /// Add an 32.768 kHz LSE to the system + pub fn lse(mut self, bypass: CrystalBypass, css: ClockSecuritySystem) -> Self { + self.lse = Some(LseConfig { bypass, css }); + + self + } + + /// Sets a frequency for the AHB bus + pub fn hclk(mut self, freq: Hertz) -> Self { + self.hclk = Some(freq.raw()); + self + } + + /// Enable the 48 MHz USB, RNG, SDMMC HSI clock source. Not available on all stm32l4x6 series + pub fn hsi48(mut self, on: bool) -> Self { + self.hsi48 = on; + self + } + + /// Enables the MSI with the specified speed + pub fn msi(mut self, range: MsiFreq) -> Self { + self.msi = Some(range); + self + } + + /// Sets LSI clock on (the default) or off + pub fn lsi(mut self, on: bool) -> Self { + self.lsi = on; + self + } + + /// Sets a frequency for the APB1 bus + pub fn pclk1(mut self, freq: Hertz) -> Self { + self.pclk1 = Some(freq.raw()); + self + } + + /// Sets a frequency for the APB2 bus + pub fn pclk2(mut self, freq: Hertz) -> Self { + self.pclk2 = Some(freq.raw()); + self + } + + /// Sets the system (core) frequency + pub fn sysclk(mut self, freq: Hertz) -> Self { + self.sysclk = Some(freq.raw()); + self + } + + /// Sets the system (core) frequency with some pll configuration + pub fn sysclk_with_pll(mut self, freq: Hertz, cfg: PllConfig) -> Self { + self.pll_config = Some(cfg); + self.sysclk = Some(freq.raw()); + self + } + + /// Sets the PLL source + pub fn pll_source(mut self, source: PllSource) -> Self { + self.pll_source = Some(source); + self + } + + /// Freezes the clock configuration, making it effective + pub fn freeze(&self, acr: &mut ACR, pwr: &mut Pwr) -> Clocks { + let rcc = unsafe { &*RCC::ptr() }; + + // Switch to MSI to prevent problems with PLL configuration. + if rcc.cr.read().msion().bit_is_clear() { + // Turn on MSI and configure it to 4MHz. + rcc.cr.modify(|_, w| { + w.msirgsel().set_bit(); // MSI Range is provided by MSIRANGE[3:0]. + w.msirange().range4m(); + w.msipllen().clear_bit(); + w.msion().set_bit() + }); + + // Wait until MSI is running + while rcc.cr.read().msirdy().bit_is_clear() {} + } + if rcc.cfgr.read().sws().bits() != 0 { + // Set MSI as a clock source, reset prescalers. + rcc.cfgr.reset(); + // Wait for clock switch status bits to change. + while rcc.cfgr.read().sws().bits() != 0 {} + } + + // + // 1. Setup clocks + // + + // Turn on the internal 32 kHz LSI oscillator + let lsi_used = match (self.lsi, &self.lse) { + (true, _) + | ( + _, + &Some(LseConfig { + bypass: _, + css: ClockSecuritySystem::Enable, + }), + ) => { + rcc.csr.modify(|_, w| w.lsion().set_bit()); + + // Wait until LSI is running + while rcc.csr.read().lsirdy().bit_is_clear() {} + + true + } + _ => false, + }; + + if let Some(lse_cfg) = &self.lse { + // 1. Unlock the backup domain + pwr.cr1.reg().modify(|_, w| w.dbp().set_bit()); + + // 2. Setup the LSE + rcc.bdcr.modify(|_, w| { + w.lseon().set_bit(); // Enable LSE + + if lse_cfg.bypass == CrystalBypass::Enable { + w.lsebyp().set_bit(); + } else { + unsafe { + w.lsedrv().bits(0b11); + } // Max drive strength, TODO: should probably be settable + } + + w + }); + + // Wait until LSE is running + while rcc.bdcr.read().lserdy().bit_is_clear() {} + + // Setup CSS + if lse_cfg.css == ClockSecuritySystem::Enable { + // Enable CSS and interrupt + rcc.bdcr.modify(|_, w| w.lsecsson().set_bit()); + rcc.cier.modify(|_, w| w.lsecssie().set_bit()); + } + } + + // If HSE is available, set it up + if let Some(hse_cfg) = &self.hse { + rcc.cr.write(|w| { + w.hseon().set_bit(); + + if hse_cfg.bypass == CrystalBypass::Enable { + w.hsebyp().set_bit(); + } + + w + }); + + while rcc.cr.read().hserdy().bit_is_clear() {} + + // Setup CSS + if hse_cfg.css == ClockSecuritySystem::Enable { + // Enable CSS + rcc.cr.modify(|_, w| w.csson().set_bit()); + } + } + + if let Some(msi) = self.msi { + unsafe { + rcc.cr.modify(|_, w| { + w.msirange() + .bits(msi as u8) + .msirgsel() + .set_bit() + .msion() + .set_bit(); + + // If LSE is enabled, enable calibration of MSI + if self.lse.is_some() { + w.msipllen().set_bit(); + } + + w + }) + }; + + // Wait until MSI is running + while rcc.cr.read().msirdy().bit_is_clear() {} + } + + // Turn on USB, RNG Clock using the HSI48 CLK source + if self.hsi48 { + // p. 180 in ref-manual + rcc.crrcr.modify(|_, w| w.hsi48on().set_bit()); + + // Wait until HSI48 is running + while rcc.crrcr.read().hsi48rdy().bit_is_clear() {} + } + + // Select MSI as clock source for usb48, rng ... + if let Some(MsiFreq::RANGE48M) = self.msi { + unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b11)) }; + } + + // + // 2. Setup PLL + // + + // Select PLL source + let (clock_speed, pll_source) = if let Some(source) = self.pll_source { + match source { + PllSource::HSE => { + if let Some(hse) = &self.hse { + (hse.speed, source) + } else { + panic!("HSE selected as PLL source, but not enabled"); + } + } + PllSource::HSI16 => (HSI, source), + PllSource::MSI => { + if let Some(msi) = self.msi { + (msi.to_hertz().raw(), source) + } else { + panic!("MSI selected as PLL source, but not enabled"); + } + } + } + } else { + // No specific PLL source selected, do educated guess + + // 1. HSE + if let Some(hse) = &self.hse { + (hse.speed, PllSource::HSE) + } + // 2. MSI + else if let Some(msi) = self.msi { + (msi.to_hertz().raw(), PllSource::MSI) + } + // 3. HSI as fallback + else { + (HSI, PllSource::HSI16) + } + }; + + // Check if HSI should be started + if pll_source == PllSource::HSI16 || (self.msi.is_none() && self.hse.is_none()) { + rcc.cr.write(|w| w.hsion().set_bit()); + while rcc.cr.read().hsirdy().bit_is_clear() {} + } + + let pllconf = if self.pll_config.is_none() { + if let Some(sysclk) = self.sysclk { + // Calculate PLL multiplier and create a best effort pll config, just multiply n + let plln = (2 * sysclk) / clock_speed; + + Some(PllConfig::new(1, plln as u8, PllDivider::Div2)) + } else { + None + } + } else { + self.pll_config + }; + + let sysclk = match (self.sysclk, self.msi) { + (Some(sysclk), _) => sysclk, + (None, Some(msi)) => msi.to_hertz().raw(), + (None, None) => MsiFreq::RANGE4M.to_hertz().raw(), + }; + + assert!(sysclk <= 80_000_000); + + let (hpre_bits, hpre_div) = self + .hclk + .map(|hclk| match sysclk / hclk { + // From p 194 in RM0394 + 0 => unreachable!(), + 1 => (0b0000, 1), + 2 => (0b1000, 2), + 3..=5 => (0b1001, 4), + 6..=11 => (0b1010, 8), + 12..=39 => (0b1011, 16), + 40..=95 => (0b1100, 64), + 96..=191 => (0b1101, 128), + 192..=383 => (0b1110, 256), + _ => (0b1111, 512), + }) + .unwrap_or((0b0000, 1)); + + let hclk = sysclk / hpre_div; + + assert!(hclk <= sysclk); + + let (ppre1_bits, ppre1) = self + .pclk1 + .map(|pclk1| match hclk / pclk1 { + // From p 194 in RM0394 + 0 => unreachable!(), + 1 => (0b000, 1), + 2 => (0b100, 2), + 3..=5 => (0b101, 4), + 6..=11 => (0b110, 8), + _ => (0b111, 16), + }) + .unwrap_or((0b000, 1)); + + let pclk1: u32 = hclk / u32(ppre1); + + assert!(pclk1 <= sysclk); + + let (ppre2_bits, ppre2) = self + .pclk2 + .map(|pclk2| match hclk / pclk2 { + // From p 194 in RM0394 + 0 => unreachable!(), + 1 => (0b000, 1), + 2 => (0b100, 2), + 3..=5 => (0b101, 4), + 6..=11 => (0b110, 8), + _ => (0b111, 16), + }) + .unwrap_or((0b000, 1)); + + let pclk2: u32 = hclk / u32(ppre2); + + assert!(pclk2 <= sysclk); + + // adjust flash wait states + unsafe { + acr.acr().write(|w| { + w.latency().bits(if hclk <= 16_000_000 { + 0b000 + } else if hclk <= 32_000_000 { + 0b001 + } else if hclk <= 48_000_000 { + 0b010 + } else if hclk <= 64_000_000 { + 0b011 + } else { + 0b100 + }) + }) + } + + let sysclk_src_bits; + let mut msi = self.msi; + if let Some(pllconf) = pllconf { + // Sanity-checks per RM0394, 6.4.4 PLL configuration register (RCC_PLLCFGR) + let r = pllconf.r.to_division_factor(); + let clock_speed = clock_speed / (pllconf.m as u32 + 1); + let vco = clock_speed * pllconf.n as u32; + let output_clock = vco / r; + + assert!(r <= 8); // Allowed max output divider + assert!(pllconf.n >= 8); // Allowed min multiplier + assert!(pllconf.n <= 86); // Allowed max multiplier + assert!(clock_speed >= 4_000_000); // VCO input clock min + assert!(clock_speed <= 16_000_000); // VCO input clock max + assert!(vco >= 64_000_000); // VCO output min + assert!(vco <= 334_000_000); // VCO output max + assert!(output_clock <= 80_000_000); // Max output clock + + // use PLL as source + sysclk_src_bits = 0b11; + rcc.cr.modify(|_, w| w.pllon().clear_bit()); + while rcc.cr.read().pllrdy().bit_is_set() {} + + let pllsrc_bits = pll_source.to_pllsrc(); + + rcc.pllcfgr.modify(|_, w| unsafe { + w.pllsrc() + .bits(pllsrc_bits) + .pllm() + .bits(pllconf.m) + .pllr() + .bits(pllconf.r.to_bits()) + .plln() + .bits(pllconf.n) + }); + + rcc.cr.modify(|_, w| w.pllon().set_bit()); + + while rcc.cr.read().pllrdy().bit_is_clear() {} + + rcc.pllcfgr.modify(|_, w| w.pllren().set_bit()); + + // SW: PLL selected as system clock + rcc.cfgr.modify(|_, w| unsafe { + w.ppre2() + .bits(ppre2_bits) + .ppre1() + .bits(ppre1_bits) + .hpre() + .bits(hpre_bits) + .sw() + .bits(sysclk_src_bits) + }); + } else { + // use MSI as fallback source for sysclk + sysclk_src_bits = 0b00; + if msi.is_none() { + msi = Some(MsiFreq::RANGE4M); + } + + // SW: MSI selected as system clock + rcc.cfgr.write(|w| unsafe { + w.ppre2() + .bits(ppre2_bits) + .ppre1() + .bits(ppre1_bits) + .hpre() + .bits(hpre_bits) + .sw() + .bits(sysclk_src_bits) + }); + } + + while rcc.cfgr.read().sws().bits() != sysclk_src_bits {} + + // + // 3. Shutdown unused clocks that have auto-started + // + + // MSI always starts on reset + if msi.is_none() { + rcc.cr + .modify(|_, w| w.msion().clear_bit().msipllen().clear_bit()) + } + + // + // 4. Clock setup done! + // + + Clocks { + hclk: hclk.Hz(), + lsi: lsi_used, + lse: self.lse.is_some(), + msi, + hsi48: self.hsi48, + pclk1: pclk1.Hz(), + pclk2: pclk2.Hz(), + ppre1, + ppre2, + sysclk: sysclk.Hz(), + pll_source: pllconf.map(|_| pll_source), + } + } +} + +#[derive(Clone, Copy, Debug)] +/// PLL output divider options +pub enum PllDivider { + /// Divider PLL output by 2 + Div2 = 0b00, + /// Divider PLL output by 4 + Div4 = 0b01, + /// Divider PLL output by 6 + Div6 = 0b10, + /// Divider PLL output by 8 + Div8 = 0b11, +} + +impl PllDivider { + #[inline(always)] + fn to_bits(self) -> u8 { + self as u8 + } + + #[inline(always)] + fn to_division_factor(self) -> u32 { + match self { + Self::Div2 => 2, + Self::Div4 => 4, + Self::Div6 => 6, + Self::Div8 => 8, + } + } +} + +#[derive(Clone, Copy, Debug)] +/// PLL Configuration +pub struct PllConfig { + // Main PLL division factor + m: u8, + // Main PLL multiplication factor + n: u8, + // Main PLL division factor for PLLCLK (system clock) + r: PllDivider, +} + +impl PllConfig { + /// Create a new PLL config from manual settings + /// + /// PLL output = ((SourceClk / input_divider) * multiplier) / output_divider + pub fn new(input_divider: u8, multiplier: u8, output_divider: PllDivider) -> Self { + assert!(input_divider > 0); + + PllConfig { + m: input_divider - 1, + n: multiplier, + r: output_divider, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +/// PLL Source +pub enum PllSource { + /// Multi-speed internal clock + MSI, + /// High-speed internal clock + HSI16, + /// High-speed external clock + HSE, +} + +impl PllSource { + fn to_pllsrc(self) -> u8 { + match self { + Self::MSI => 0b01, + Self::HSI16 => 0b10, + Self::HSE => 0b11, + } + } +} + +/// Frozen clock frequencies +/// +/// The existence of this value indicates that the clock configuration can no longer be changed +#[derive(Clone, Copy, Debug)] +pub struct Clocks { + hclk: Hertz, + hsi48: bool, + msi: Option, + lsi: bool, + lse: bool, + pclk1: Hertz, + pclk2: Hertz, + ppre1: u8, + ppre2: u8, + sysclk: Hertz, + pll_source: Option, +} + +impl Clocks { + /// Returns the frequency of the AHB + pub fn hclk(&self) -> Hertz { + self.hclk + } + + /// Returns status of HSI48 + pub fn hsi48(&self) -> bool { + self.hsi48 + } + + // Returns the status of the MSI + pub fn msi(&self) -> Option { + self.msi + } + + /// Returns status of the LSI + pub fn lsi(&self) -> bool { + self.lsi + } + + // Return the status of the LSE + pub fn lse(&self) -> bool { + self.lse + } + + /// Returns the frequency of the APB1 + pub fn pclk1(&self) -> Hertz { + self.pclk1 + } + + /// Returns the frequency of the APB2 + pub fn pclk2(&self) -> Hertz { + self.pclk2 + } + + /// Get which source is being used for PLL + pub fn pll_source(&self) -> Option { + self.pll_source + } + + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn ppre1(&self) -> u8 { + self.ppre1 + } + // TODO remove `allow` + #[allow(dead_code)] + pub(crate) fn ppre2(&self) -> u8 { + self.ppre2 + } + + /// Returns the system (core) frequency + pub fn sysclk(&self) -> Hertz { + self.sysclk + } +} diff --git a/stm32l4xx-hal/src/rcc/enable.rs b/stm32l4xx-hal/src/rcc/enable.rs new file mode 100644 index 0000000..79c9baa --- /dev/null +++ b/stm32l4xx-hal/src/rcc/enable.rs @@ -0,0 +1,376 @@ +use super::*; + +macro_rules! bus_enable { + ($PER:ident => $en:ident) => { + impl Enable for crate::pac::$PER { + #[inline(always)] + fn enable(bus: &mut Self::Bus) { + bus.enr().modify(|_, w| w.$en().set_bit()); + // Stall the pipeline to work around erratum 2.1.13 (DM00037591) + cortex_m::asm::dsb(); // TODO: check if needed + } + #[inline(always)] + fn disable(bus: &mut Self::Bus) { + bus.enr().modify(|_, w| w.$en().clear_bit()); + } + #[inline(always)] + fn is_enabled() -> bool { + Self::Bus::new().enr().read().$en().bit_is_set() + } + #[inline(always)] + fn is_disabled() -> bool { + Self::Bus::new().enr().read().$en().bit_is_clear() + } + #[inline(always)] + unsafe fn enable_unchecked() { + Self::enable(&mut Self::Bus::new()); + } + #[inline(always)] + unsafe fn disable_unchecked() { + Self::disable(&mut Self::Bus::new()); + } + } + }; +} + +macro_rules! bus_smenable { + ($PER:ident => $smen:ident) => { + impl SMEnable for crate::pac::$PER { + #[inline(always)] + fn enable_in_sleep_mode(bus: &mut Self::Bus) { + bus.smenr().modify(|_, w| w.$smen().set_bit()); + // Stall the pipeline to work around erratum 2.1.13 (DM00037591) + cortex_m::asm::dsb(); + } + #[inline(always)] + fn disable_in_sleep_mode(bus: &mut Self::Bus) { + bus.smenr().modify(|_, w| w.$smen().clear_bit()); + } + #[inline(always)] + fn is_enabled_in_sleep_mode() -> bool { + Self::Bus::new().smenr().read().$smen().bit_is_set() + } + #[inline(always)] + fn is_disabled_in_sleep_mode() -> bool { + Self::Bus::new().smenr().read().$smen().bit_is_clear() + } + #[inline(always)] + unsafe fn enable_in_sleep_mode_unchecked() { + Self::enable(&mut Self::Bus::new()); + } + #[inline(always)] + unsafe fn disable_in_sleep_mode_unchecked() { + Self::disable(&mut Self::Bus::new()); + } + } + }; +} +macro_rules! bus_reset { + ($PER:ident => $rst:ident) => { + impl Reset for crate::pac::$PER { + #[inline(always)] + fn reset(bus: &mut Self::Bus) { + bus.rstr().modify(|_, w| w.$rst().set_bit()); + bus.rstr().modify(|_, w| w.$rst().clear_bit()); + } + #[inline(always)] + unsafe fn reset_unchecked() { + Self::reset(&mut Self::Bus::new()); + } + } + }; +} + +macro_rules! bus { + ($($PER:ident => ($busX:ty, $($en:ident)?, $($smen:ident)?, $($rst:ident)?),)+) => { + $( + impl crate::Sealed for crate::pac::$PER {} + impl RccBus for crate::pac::$PER { + type Bus = $busX; + } + $(bus_enable!($PER => $en);)? + $(bus_smenable!($PER => $smen);)? + $(bus_reset!($PER => $rst);)? + )+ + }; +} + +bus! { + DMA1 => (AHB1, dma1en, dma1smen, dma1rst), // 0 + DMA2 => (AHB1, dma2en, dma2smen, dma2rst), // 1 + FLASH => (AHB1, flashen, flashsmen, flashrst), // 8 + CRC => (AHB1, crcen, crcsmen, crcrst), // 12 + TSC => (AHB1, tscen, tscsmen, tscrst), // 16 + + GPIOA => (AHB2, gpioaen, gpioasmen, gpioarst), // 0 + GPIOB => (AHB2, gpioben, gpiobsmen, gpiobrst), // 1 + GPIOC => (AHB2, gpiocen, gpiocsmen, gpiocrst), // 2 + GPIOD => (AHB2, gpioden, gpiodsmen, gpiodrst), // 3 + GPIOE => (AHB2, gpioeen, gpioesmen, gpioerst), // 4 + GPIOH => (AHB2, gpiohen, gpiohsmen, gpiohrst), // 7 + AES => (AHB2, aesen, aessmen, aesrst), // 16 + RNG => (AHB2, rngen, rngsmen, rngrst), // 18 + + TIM2 => (APB1R1, tim2en, tim2smen, tim2rst), // 0 + TIM6 => (APB1R1, tim6en, tim6smen, tim6rst), // 4 + TIM7 => (APB1R1, tim7en, tim7smen, tim7rst), // 5 + WWDG => (APB1R1, wwdgen, wwdgsmen,), // 11 + SPI2 => (APB1R1, spi2en, spi2smen, spi2rst), // 14 + SPI3 => (APB1R1, spi3en, sp3smen, spi3rst), // 15 // TODO: fix typo + USART2 => (APB1R1, usart2en, usart2smen, usart2rst), // 17 + USART3 => (APB1R1, usart3en, usart3smen, usart3rst), // 18 + I2C1 => (APB1R1, i2c1en, i2c1smen, i2c1rst), // 21 + I2C2 => (APB1R1, i2c2en, i2c2smen, i2c2rst), // 22 + I2C3 => (APB1R1, i2c3en, i2c3smen, i2c3rst), // 23 + CAN1 => (APB1R1, can1en, can1smen, can1rst), // 25 + PWR => (APB1R1, pwren, pwrsmen, pwrrst), // 28 + OPAMP => (APB1R1, opampen, opampsmen, opamprst), // 30 + LPTIM1 => (APB1R1, lptim1en, lptim1smen, lptim1rst), // 31 + + LPUART1 => (APB1R2, lpuart1en, lpuart1smen, lpuart1rst), // 0 + LPTIM2 => (APB1R2, lptim2en, lptim2smen, lptim2rst), // 5 + + SYSCFG => (APB2, syscfgen, syscfgsmen, syscfgrst), // 0 + TIM1 => (APB2, tim1en, tim1smen, tim1rst), // 11 + SPI1 => (APB2, spi1en, spi1smen, spi1rst), // 12 + USART1 => (APB2, usart1en, usart1smen, usart1rst), // 14 + TIM15 => (APB2, tim15en, tim15smen, tim15rst), // 16 + TIM16 => (APB2, tim16en, tim16smen, tim16rst), // 17 + SAI1 => (APB2, sai1en, sai1smen, sai1rst), // 21 +} + +// L4x1, L4x2, L4x3, L4x5 or L4x6 +#[cfg(not(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +)))] +bus! { + ADC1 => (AHB2, adcen, adcfssmen, adcrst), // 13 + + LCD => (APB1R1, lcden, lcdsmen, lcdrst), // 9 + + SWPMI1 => (APB1R2, swpmi1en, swpmi1smen, swpmi1rst), // 2 + + FIREWALL => (APB2, firewallen,,), // 7 +} + +// L4+ +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +bus! { + ADC => (AHB2, adcen, adcfssmen, adcrst), // 13 + + FIREWALL => (APB2, fwen,,), // 7 + LTCD => (APB2, ltdcen, ltdcsmen, ltdcrst), // 26 +} + +// L4x5 or L4x6 +#[cfg(any( + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +bus! { + GPIOF => (AHB2, gpiofen, gpiofsmen, gpiofrst), // 5 + GPIOG => (AHB2, gpiogen, gpiogsmen, gpiogrst), // 6 + + FMC => (AHB3, fmcen, fmcsmen, fmcrst), // 0 + + TIM3 => (APB1R1, tim3en, tim3smen, tim3rst), // 1 + TIM4 => (APB1R1, tim4en, tim4smen, tim4rst), // 2 + TIM5 => (APB1R1, tim5en, tim5smen, tim5rst), // 3 + UART4 => (APB1R1, uart4en, uart4smen, uart4rst), // 19 + UART5 => (APB1R1, uart5en, uart5smen, uart5rst), // 20 + + TIM8 => (APB2, tim8en, tim8smen, tim8rst), // 13 + TIM17 => (APB2, tim17en, tim17smen, tim17rst), // 18 + SAI2 => (APB2, sai2en, sai2smen, sai2rst), // 22 +} + +// L4x1 or L4x2 +#[cfg(any( + feature = "stm32l431", + feature = "stm32l451", + feature = "stm32l471", + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", +))] +bus! { + UART4 => (APB1R1, uart4en, uart4smen, usart4rst), // 19 // TODO: fix typo + + I2C4 => (APB1R2, i2c4en,, i2c4rst), // 1 // TODO: fix absent +} + +// L4x1, L4x2, L4x3, or L4x5 +#[cfg(any( + feature = "stm32l431", + feature = "stm32l451", + feature = "stm32l471", + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443", + feature = "stm32l475", +))] +bus! { + DAC1 => (APB1R1, dac1en, dac1smen, dac1rst), // 29 + + SDMMC => (APB2, sdmmcen, sdmmcsmen, sdmmcrst), // 10 +} + +// L4x1, L4x2, L4x5, or L4x6 +#[cfg(not(any( + feature = "stm32l433", + feature = "stm32l443", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", + )))] +bus! { + ADC2 => (AHB2, adcen, adcfssmen, adcrst), // 13 + QUADSPI => (AHB3, qspien, qspismen, qspirst), // 8 +} + +// L4x1, L4x2, L4x3, or L4x6 (L4+ assumed) +#[cfg(not(any(feature = "stm32l475",)))] +bus! { + CRS => (APB1R1, crsen,,), // 24 // TODO: fix absent +} + +// L4x1, or L4x3 +#[cfg(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443", +))] +bus! { + USB => (APB1R1, usbfsen, usbfssmen, usbfsrst), // 26 +} + +// L4x1 +#[cfg(any(feature = "stm32l431", feature = "stm32l451", feature = "stm32l471",))] +bus! { + TIM3 => (APB1R1, tim3en,,), // 1 // TODO: absent smen, rst + USB_FS => (APB1R1, usbf, usbfssmen, usbfsrst), // 26 // TODO: fix typo +} + +// L4x2 +#[cfg(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", +))] +bus! { + TIM3 => (APB1R1, tim3en,, tim3rst), // 1 // TODO: fix absent +} + +// L4x5 +#[cfg(any(feature = "stm32l475"))] +bus! { + DFSDM => (APB2, dfsdmen, dfsdmsmen, dfsdmrst), // 24 +} + +// L4x6 (L4+ assumed) +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +bus! { + DMA2D => (AHB1, dma2den, dma2dsmen, dma2drst), // 17 + + GPIOI => (AHB2, gpioien, gpioismen, gpioirst), // 8 + OTG_FS_GLOBAL => (AHB2, otgfsen, otgfssmen, otgfsrst), // 12 // TODO: absent in x5 + DCMI => (AHB2, dcmien, dcmismen, dcmirst), // 14 + + DAC => (APB1R1, dac1en, dac1smen, dac1rst), // 29 + + I2C4 => (APB1R2, i2c4en, i2c4smen, i2c4rst), // 1 +} + +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", +))] +bus! { + CAN2 => (APB1R1, can2en, can2smen, can2rst), // 26 + + HASH => (AHB2, hash1en, hash1smen, hash1rst), // 17 + + SDMMC1 => (APB2, sdmmcen, sdmmcsmen, sdmmcrst), // 10 + DFSDM1 => (APB2, dfsdmen, dfsdmsmen, dfsdmrst), // 24 +} + +#[cfg(any( + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +bus! { + HASH => (AHB2, hashen, hashsmen, hashrst), // 17 + SDMMC1 => (AHB2, sdmmc1en, sdmmc1smen, sdmmc1rst), // 22 + + DFSDM1 => (APB2, dfsdm1en, dfsdm1smen, dfsdm1rst), // 24 +} diff --git a/stm32l4xx-hal/src/rng.rs b/stm32l4xx-hal/src/rng.rs new file mode 100644 index 0000000..1bff789 --- /dev/null +++ b/stm32l4xx-hal/src/rng.rs @@ -0,0 +1,137 @@ +extern crate core; +#[cfg(feature = "unproven")] +use core::cmp; + +use crate::rcc::{Clocks, Enable, AHB2}; +use crate::stm32::RNG; +pub use rand_core::{CryptoRng, RngCore}; + +/// Extension trait to activate the RNG +pub trait RngExt { + /// Enables the RNG + fn enable(self, ahb2: &mut AHB2, clocks: Clocks) -> Rng; +} + +impl RngExt for RNG { + fn enable(self, ahb2: &mut AHB2, clocks: Clocks) -> Rng { + // crrcr.crrcr().modify(|_, w| w.hsi48on().set_bit()); // p. 180 in ref-manual + // ...this is now supposed to be done in RCC configuration before freezing + + // hsi48 should be turned on previously or msi at 48mhz + let msi = match clocks.msi() { + Some(msi) => msi == crate::rcc::MsiFreq::RANGE48M, + None => false, + }; + let hsi = clocks.hsi48(); + assert!(msi || hsi); + + ::enable(ahb2); + // if we don't do this... we can be "too fast", and + // the following setting of rng.cr.rngen has no effect!! + while !RNG::is_enabled() {} + + self.cr.modify(|_, w| w.rngen().set_bit()); + + Rng { rng: self } + } +} + +/// Constrained RNG peripheral +pub struct Rng { + rng: RNG, +} + +impl Rng { + // cf. https://github.com/nrf-rs/nrf51-hal/blob/master/src/rng.rs#L31 + pub fn free(self) -> RNG { + // maybe disable the RNG? + self.rng + } + + // various methods that are not in the blessed embedded_hal + // trait list, but may be helpful nonetheless + // Q: should these be prefixed by underscores? + + pub fn get_random_data(&self) -> u32 { + while !self.is_data_ready() {} + self.possibly_invalid_random_data() + // NB: no need to clear bit here + } + + // RNG_CR + /* missing in stm32l4... + pub fn is_clock_error_detection_enabled(&self) -> bool { + self.rng.cr.read().ced().bit() + } + */ + + pub fn is_interrupt_enabled(&self) -> bool { + self.rng.cr.read().ie().bit() + } + + pub fn is_enabled(&self) -> bool { + self.rng.cr.read().rngen().bit() + } + + // RNG_SR + pub fn is_clock_error(&self) -> bool { + self.rng.sr.read().cecs().bit() + } + + pub fn is_seed_error(&self) -> bool { + self.rng.sr.read().secs().bit() + } + + pub fn is_data_ready(&self) -> bool { + self.rng.sr.read().drdy().bit() + } + + // RNG_DR + pub fn possibly_invalid_random_data(&self) -> u32 { + self.rng.dr.read().rndata().bits() + } +} + +impl RngCore for Rng { + fn next_u32(&mut self) -> u32 { + self.get_random_data() + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_u32(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + rand_core::impls::fill_bytes_via_next(self, dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + Ok(self.fill_bytes(dest)) + } +} + +impl CryptoRng for Rng {} + +#[derive(Debug)] +pub enum Error {} + +#[cfg(feature = "unproven")] +impl crate::hal::blocking::rng::Read for Rng { + // TODO: this error seems pretty useless if it + // doesn't flag non-enabled RNG or non-started HSI48, + // but that would be a runtime cost :/ + type Error = Error; + + fn read(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> { + let mut i = 0usize; + while i < buffer.len() { + let random_word: u32 = self.get_random_data(); + let bytes: [u8; 4] = random_word.to_ne_bytes(); + let n = cmp::min(4, buffer.len() - i); + buffer[i..i + n].copy_from_slice(&bytes[..n]); + i += n; + } + + Ok(()) + } +} diff --git a/stm32l4xx-hal/src/rtc.rs b/stm32l4xx-hal/src/rtc.rs new file mode 100644 index 0000000..8f9e6b1 --- /dev/null +++ b/stm32l4xx-hal/src/rtc.rs @@ -0,0 +1,724 @@ +//! RTC peripheral abstraction + +/// refer to AN4759 to compare features of RTC2 and RTC3 +#[cfg(not(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l4p5", + feature = "stm32l4q5" +)))] +pub mod rtc2; +#[cfg(not(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l4p5", + feature = "stm32l4q5" +)))] +pub use rtc2 as rtc_registers; + +/// refer to AN4759 to compare features of RTC2 and RTC3 +#[cfg(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l4p5", + feature = "stm32l4q5" +))] +pub mod rtc3; +#[cfg(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l4p5", + feature = "stm32l4q5" +))] +pub use rtc3 as rtc_registers; + +use fugit::ExtU32; +use void::Void; + +use crate::{ + datetime::*, + hal::timer::{self, Cancel as _}, + pwr, + rcc::{APB1R1, BDCR}, + stm32::{EXTI, RTC}, +}; + +/// Interrupt event +pub enum Event { + WakeupTimer, + AlarmA, + AlarmB, + Timestamp, +} + +pub enum Alarm { + AlarmA, + AlarmB, +} + +impl From for Event { + fn from(a: Alarm) -> Self { + match a { + Alarm::AlarmA => Event::AlarmA, + Alarm::AlarmB => Event::AlarmB, + } + } +} + +/// RTC Abstraction +pub struct Rtc { + rtc: RTC, + rtc_config: RtcConfig, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcClockSource { + /// 00: No clock + NoClock = 0b00, + /// 01: LSE oscillator clock used as RTC clock + LSE = 0b01, + /// 10: LSI oscillator clock used as RTC clock + LSI = 0b10, + /// 11: HSE oscillator clock divided by 32 used as RTC clock + HSE = 0b11, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcWakeupClockSource { + /// RTC/16 clock is selected + RtcClkDiv16 = 0b000, + /// RTC/8 clock is selected + RtcClkDiv8 = 0b001, + /// RTC/4 clock is selected + RtcClkDiv4 = 0b010, + /// RTC/2 clock is selected + RtcClkDiv2 = 0b011, + /// ck_spre (usually 1 Hz) clock is selected. Handling of the 2 ** 16 bit is done if values + /// larger than 2 ** 16 are passed to the timer start function. + CkSpre = 0b100, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct RtcConfig { + /// RTC clock source + clock_config: RtcClockSource, + /// Wakeup clock source + wakeup_clock_config: RtcWakeupClockSource, + /// Asynchronous prescaler factor + /// This is the asynchronous division factor: + /// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1) + /// ck_apre drives the subsecond register + async_prescaler: u8, + /// Synchronous prescaler factor + /// This is the synchronous division factor: + /// ck_spre frequency = ck_apre frequency/(PREDIV_S+1) + /// ck_spre must be 1Hz + sync_prescaler: u16, +} + +impl Default for RtcConfig { + /// LSI with prescalers assuming 32.768 kHz. + /// Raw sub-seconds in 1/256. + fn default() -> Self { + RtcConfig { + clock_config: RtcClockSource::LSI, + wakeup_clock_config: RtcWakeupClockSource::CkSpre, + async_prescaler: 127, + sync_prescaler: 255, + } + } +} + +impl RtcConfig { + /// Sets the clock source of RTC config + pub fn clock_config(mut self, cfg: RtcClockSource) -> Self { + self.clock_config = cfg; + self + } + + /// Set the asynchronous prescaler of RTC config + pub fn async_prescaler(mut self, prescaler: u8) -> Self { + self.async_prescaler = prescaler; + self + } + + /// Set the synchronous prescaler of RTC config + pub fn sync_prescaler(mut self, prescaler: u16) -> Self { + self.sync_prescaler = prescaler; + self + } + + /// Set the Clock Source for the Wakeup Timer + pub fn wakeup_clock_config(mut self, cfg: RtcWakeupClockSource) -> Self { + self.wakeup_clock_config = cfg; + self + } +} + +impl Rtc { + pub fn rtc( + rtc: RTC, + apb1r1: &mut APB1R1, + bdcr: &mut BDCR, + pwrcr1: &mut pwr::CR1, + rtc_config: RtcConfig, + ) -> Self { + // assert_eq!(clocks.lsi(), true); // make sure LSI is enabled + // enable peripheral clock for communication + apb1r1.enr().modify(|_, w| w.rtcapben().set_bit()); + pwrcr1.reg().read(); // read to allow the pwr clock to enable + + let mut rtc_struct = Self { rtc, rtc_config }; + rtc_struct.set_config(bdcr, pwrcr1, rtc_config); + + rtc_struct + } + + /// Get date and time touple + pub fn get_date_time(&self) -> (Date, Time) { + let time; + let date; + + let sync_p = self.rtc_config.sync_prescaler as u32; + let micros = + 1_000_000u32 / (sync_p + 1) * (sync_p - self.rtc.ssr.read().ss().bits() as u32); + let timer = self.rtc.tr.read(); + let cr = self.rtc.cr.read(); + + // Reading either RTC_SSR or RTC_TR locks the values in the higher-order + // calendar shadow registers until RTC_DR is read. + let dater = self.rtc.dr.read(); + + time = Time::new( + (bcd2_to_byte((timer.ht().bits(), timer.hu().bits())) as u32).hours(), + (bcd2_to_byte((timer.mnt().bits(), timer.mnu().bits())) as u32).minutes(), + (bcd2_to_byte((timer.st().bits(), timer.su().bits())) as u32).secs(), + micros.micros(), + cr.bkp().bit(), + ); + + date = Date::new( + dater.wdu().bits().into(), + bcd2_to_byte((dater.dt().bits(), dater.du().bits())).into(), + bcd2_to_byte((dater.mt().bit() as u8, dater.mu().bits())).into(), + (bcd2_to_byte((dater.yt().bits(), dater.yu().bits())) as u16 + 1970_u16).into(), + ); + + (date, time) + } + + /// Set Date and Time + pub fn set_date_time(&mut self, date: Date, time: Time) { + self.write(true, |rtc| { + set_time_raw(rtc, time); + set_date_raw(rtc, date); + }) + } + + /// Set Time + /// Note: If setting both time and date, use set_date_time(...) to avoid errors. + pub fn set_time(&mut self, time: Time) { + self.write(true, |rtc| { + set_time_raw(rtc, time); + }) + } + + /// Set Date + /// Note: If setting both time and date, use set_date_time(...) to avoid errors. + pub fn set_date(&mut self, date: Date) { + self.write(true, |rtc| { + set_date_raw(rtc, date); + }) + } + + pub fn get_config(&self) -> RtcConfig { + self.rtc_config + } + + /// Sets the time at which an alarm will be triggered + /// This also clears the alarm flag if it is set + pub fn set_alarm(&mut self, alarm: Alarm, date: Date, time: Time) { + let (dt, du) = byte_to_bcd2(date.date as u8); + let (ht, hu) = byte_to_bcd2(time.hours as u8); + let (mnt, mnu) = byte_to_bcd2(time.minutes as u8); + let (st, su) = byte_to_bcd2(time.seconds as u8); + + self.write(false, |rtc| match alarm { + Alarm::AlarmA => { + rtc.cr.modify(|_, w| w.alrae().clear_bit()); // Disable Alarm A + rtc_registers::clear_alarm_a_flag(rtc); + while !rtc_registers::is_alarm_a_accessible(rtc) {} + + rtc.alrmar.modify(|_, w| unsafe { + w.dt() + .bits(dt) + .du() + .bits(du) + .ht() + .bits(ht) + .hu() + .bits(hu) + .mnt() + .bits(mnt) + .mnu() + .bits(mnu) + .st() + .bits(st) + .su() + .bits(su) + .pm() + .clear_bit() + .wdsel() + .clear_bit() + }); + // binary mode alarm not implemented (RTC3 only) + // subsecond alarm not implemented + // would need a conversion method between `time.micros` and RTC ticks + // write the SS value and mask to `rtc.alrmassr` + + // enable alarm and reenable interrupt if it was enabled + rtc.cr.modify(|_, w| w.alrae().set_bit()); + } + Alarm::AlarmB => { + rtc.cr.modify(|_, w| w.alrbe().clear_bit()); + + rtc_registers::clear_alarm_b_flag(rtc); + while !rtc_registers::is_alarm_b_accessible(rtc) {} + + rtc.alrmbr.modify(|_, w| unsafe { + w.dt() + .bits(dt) + .du() + .bits(du) + .ht() + .bits(ht) + .hu() + .bits(hu) + .mnt() + .bits(mnt) + .mnu() + .bits(mnu) + .st() + .bits(st) + .su() + .bits(su) + .pm() + .clear_bit() + .wdsel() + .clear_bit() + }); + // binary mode alarm not implemented (RTC3 only) + // subsecond alarm not implemented + // would need a conversion method between `time.micros` and RTC ticks + // write the SS value and mask to `rtc.alrmbssr` + + // enable alarm and reenable interrupt if it was enabled + rtc.cr.modify(|_, w| w.alrbe().set_bit()); + } + }); + } + + /// Starts listening for an interrupt event + pub fn listen(&mut self, exti: &mut EXTI, event: Event) { + self.write(false, |rtc| match event { + Event::WakeupTimer => { + exti.rtsr1.modify(|_, w| w.tr20().set_bit()); + exti.imr1.modify(|_, w| w.mr20().set_bit()); + rtc.cr.modify(|_, w| w.wutie().set_bit()) + } + Event::AlarmA => { + // Workaround until tr17() is implemented () + exti.rtsr1.modify(|_, w| w.tr18().set_bit()); + exti.imr1.modify(|_, w| w.mr18().set_bit()); + rtc.cr.modify(|_, w| w.alraie().set_bit()) + } + Event::AlarmB => { + exti.rtsr1.modify(|_, w| w.tr18().set_bit()); + exti.imr1.modify(|_, w| w.mr18().set_bit()); + rtc.cr.modify(|_, w| w.alrbie().set_bit()) + } + Event::Timestamp => { + exti.rtsr1.modify(|_, w| w.tr19().set_bit()); + exti.imr1.modify(|_, w| w.mr19().set_bit()); + rtc.cr.modify(|_, w| w.tsie().set_bit()) + } + }) + } + + /// Stops listening for an interrupt event + pub fn unlisten(&mut self, exti: &mut EXTI, event: Event) { + self.write(false, |rtc| match event { + Event::WakeupTimer => { + exti.rtsr1.modify(|_, w| w.tr20().clear_bit()); + exti.imr1.modify(|_, w| w.mr20().clear_bit()); + rtc.cr.modify(|_, w| w.wutie().clear_bit()) + } + Event::AlarmA => { + // Workaround until tr17() is implemented () + exti.rtsr1.modify(|_, w| w.tr18().clear_bit()); + exti.imr1.modify(|_, w| w.mr18().clear_bit()); + rtc.cr.modify(|_, w| w.alraie().clear_bit()) + } + Event::AlarmB => { + exti.rtsr1.modify(|_, w| w.tr18().clear_bit()); + exti.imr1.modify(|_, w| w.mr18().clear_bit()); + rtc.cr.modify(|_, w| w.alrbie().clear_bit()) + } + Event::Timestamp => { + exti.rtsr1.modify(|_, w| w.tr19().clear_bit()); + exti.imr1.modify(|_, w| w.mr19().clear_bit()); + rtc.cr.modify(|_, w| w.tsie().clear_bit()) + } + }) + } + + /// Checks for an interrupt event + pub fn check_interrupt(&mut self, event: Event, clear: bool) -> bool { + let result = match event { + Event::WakeupTimer => rtc_registers::is_wakeup_timer_flag_set(&self.rtc), + Event::AlarmA => rtc_registers::is_alarm_a_flag_set(&self.rtc), + Event::AlarmB => rtc_registers::is_alarm_b_flag_set(&self.rtc), + Event::Timestamp => rtc_registers::is_timestamp_flag_set(&self.rtc), + }; + if clear { + self.write(false, |rtc| match event { + Event::WakeupTimer => { + rtc_registers::clear_wakeup_timer_flag(rtc); + unsafe { (*EXTI::ptr()).pr1.write(|w| w.bits(1 << 20)) }; + } + Event::AlarmA => { + rtc_registers::clear_alarm_a_flag(rtc); + unsafe { (*EXTI::ptr()).pr1.write(|w| w.bits(1 << 18)) }; + } + Event::AlarmB => { + rtc_registers::clear_alarm_b_flag(rtc); + unsafe { (*EXTI::ptr()).pr1.write(|w| w.bits(1 << 18)) }; + } + Event::Timestamp => { + rtc_registers::clear_timestamp_flag(rtc); + unsafe { (*EXTI::ptr()).pr1.write(|w| w.bits(1 << 19)) }; + } + }) + } + + result + } + + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub fn set_config(&mut self, bdcr: &mut BDCR, pwrcr1: &mut pwr::CR1, rtc_config: RtcConfig) { + // Unlock the backup domain + pwrcr1.reg().modify(|_, w| w.dbp().set_bit()); + while pwrcr1.reg().read().dbp().bit_is_clear() {} + + let reg = bdcr.enr().read(); + assert!( + !reg.lsecsson().bit(), + "RTC is not compatible with LSE CSS, yet." + ); + + if !reg.rtcen().bit() || reg.rtcsel().bits() != rtc_config.clock_config as u8 { + bdcr.enr().modify(|_, w| w.bdrst().set_bit()); + + bdcr.enr().modify(|_, w| unsafe { + // Reset + w.bdrst().clear_bit(); + // Select RTC source + w.rtcsel() + .bits(rtc_config.clock_config as u8) + .rtcen() + .set_bit(); + + // Restore bcdr + w.lscosel() + .bit(reg.lscosel().bit()) + .lscoen() + .bit(reg.lscoen().bit()); + + w.lseon() + .bit(reg.lseon().bit()) + .lsedrv() + .bits(reg.lsedrv().bits()) + .lsebyp() + .bit(reg.lsebyp().bit()) + }); + } + + self.write(true, |rtc| { + rtc.cr.modify(|_, w| unsafe { + w.fmt() + .clear_bit() // 24hr + .osel() + /* + 00: Output disabled + 01: Alarm A output enabled + 10: Alarm B output enabled + 11: Wakeup output enabled + */ + .bits(0b00) + .pol() + .clear_bit() // pol high + }); + + rtc.prer.modify(|_, w| unsafe { + w.prediv_s() + .bits(rtc_config.sync_prescaler) + .prediv_a() + .bits(rtc_config.async_prescaler) + }); + + // TODO configuration for output pins + rtc_registers::reset_gpio(rtc); + }); + + self.rtc_config = rtc_config; + } + + /// Access the wakeup timer + pub fn wakeup_timer(&mut self) -> WakeupTimer { + WakeupTimer { rtc: self } + } + + fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&RTC) -> R, + { + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + self.rtc.wpr.write(|w| unsafe { w.key().bits(0xca) }); + self.rtc.wpr.write(|w| unsafe { w.key().bits(0x53) }); + + if init_mode && !rtc_registers::is_init_mode(&self.rtc) { + rtc_registers::enter_init_mode(&self.rtc); + // wait till init state entered + // ~2 RTCCLK cycles + while !rtc_registers::is_init_mode(&self.rtc) {} + } + + let result = f(&self.rtc); + if init_mode { + rtc_registers::exit_init_mode(&self.rtc); + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + self.rtc.wpr.write(|w| unsafe { w.key().bits(0xff) }); + + result + } + + pub const BACKUP_REGISTER_COUNT: usize = rtc_registers::BACKUP_REGISTER_COUNT; + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn read_backup_register(&self, register: usize) -> Option { + rtc_registers::read_backup_register(&self.rtc, register) + } + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn write_backup_register(&self, register: usize, value: u32) { + rtc_registers::write_backup_register(&self.rtc, register, value) + } +} + +/// The RTC wakeup timer +/// +/// This timer can be used in two ways: +/// 1. Continually call `wait` until it returns `Ok(())`. +/// 2. Set up the RTC interrupt. +/// +/// If you use an interrupt, you should still call `wait` once, after the +/// interrupt fired. This should return `Ok(())` immediately. Doing this will +/// reset the timer flag. If you don't do this, the interrupt will not fire +/// again, if you go to sleep. +/// +/// You don't need to call `wait`, if you call `cancel`, as that also resets the +/// flag. Restarting the timer by calling `start` will also reset the flag. +pub struct WakeupTimer<'r> { + rtc: &'r mut Rtc, +} + +impl timer::Periodic for WakeupTimer<'_> {} + +impl timer::CountDown for WakeupTimer<'_> { + type Time = u32; + + /// Starts the wakeup timer + /// + /// The `delay` argument specifies the timer delay. If the wakeup_clock_config is set to + /// CkSpre, the value is in seconds and up to 17 bits + /// of delay are supported, giving us a range of over 36 hours. + /// Otherwise, the timeunit depends on the RTCCLK and the configured wakeup_clock_config value. + /// + /// # Panics + /// + /// The `delay` argument must be in the range `1 <= delay <= 2^17`. + /// Panics, if `delay` is outside of that range. + fn start(&mut self, delay: T) + where + T: Into, + { + let delay = delay.into(); + assert!(1 <= delay); + + if self.rtc.rtc_config.wakeup_clock_config == RtcWakeupClockSource::CkSpre { + assert!(delay <= 1 << 17); + } else { + assert!(delay <= 1 << 16); + } + + // Determine the value for the wucksel register + let wucksel = self.rtc.rtc_config.wakeup_clock_config as u8; + let wucksel = wucksel + | if self.rtc.rtc_config.wakeup_clock_config == RtcWakeupClockSource::CkSpre + && delay & 0x1_00_00 != 0 + { + 0b010 + } else { + 0b000 + }; + + let delay = delay - 1; + + // Can't panic, as the error type is `Void`. + self.cancel().unwrap(); + + self.rtc.write(false, |rtc| { + // Set the wakeup delay + rtc.wutr.write(|w| + // Write the lower 16 bits of `delay`. The 17th bit is taken + // care of via WUCKSEL in CR (see below). + // This is safe, as the field accepts a full 16 bit value. + unsafe { w.wut().bits(delay as u16) }); + + rtc.cr.modify(|_, w| { + // Write WUCKSEL depending on value determined previously. + unsafe { + w.wucksel().bits(wucksel); + } + // Enable wakeup timer + w.wute().set_bit() + }); + }); + + // Let's wait for WUTWF to clear. Otherwise we might run into a race + // condition, if the user calls this method again really quickly. + while rtc_registers::is_wakeup_timer_write_flag_set(&self.rtc.rtc) {} + } + + fn wait(&mut self) -> nb::Result<(), Void> { + if self.rtc.check_interrupt(Event::WakeupTimer, true) { + return Ok(()); + } + + Err(nb::Error::WouldBlock) + } +} + +impl timer::Cancel for WakeupTimer<'_> { + type Error = Void; + + fn cancel(&mut self) -> Result<(), Self::Error> { + self.rtc.write(false, |rtc| { + // Disable the wakeup timer + rtc.cr.modify(|_, w| w.wute().clear_bit()); + while !rtc_registers::is_wakeup_timer_write_flag_set(rtc) {} + rtc_registers::clear_wakeup_timer_flag(rtc); + + // According to the reference manual, section 26.7.4, the WUTF flag + // must be cleared at least 1.5 RTCCLK periods "before WUTF is set + // to 1 again". If that's true, we're on the safe side, because we + // use ck_spre as the clock for this timer, which we've scaled to 1 + // Hz. + // + // I have the sneaking suspicion though that this is a typo, and the + // quote in the previous paragraph actually tries to refer to WUTE + // instead of WUTF. In that case, this might be a bug, so if you're + // seeing something weird, adding a busy loop of some length here + // would be a good start of your investigation. + }); + + Ok(()) + } +} + +/// Raw set time +/// Expects init mode enabled and write protection disabled +fn set_time_raw(rtc: &RTC, time: Time) { + let (ht, hu) = byte_to_bcd2(time.hours as u8); + let (mnt, mnu) = byte_to_bcd2(time.minutes as u8); + let (st, su) = byte_to_bcd2(time.seconds as u8); + + rtc.tr.write(|w| unsafe { + w.ht() + .bits(ht) + .hu() + .bits(hu) + .mnt() + .bits(mnt) + .mnu() + .bits(mnu) + .st() + .bits(st) + .su() + .bits(su) + .pm() + .clear_bit() + }); + + rtc.cr.modify(|_, w| w.bkp().bit(time.daylight_savings)); +} + +/// Raw set date +/// Expects init mode enabled and write protection disabled +fn set_date_raw(rtc: &RTC, date: Date) { + let (dt, du) = byte_to_bcd2(date.date as u8); + let (mt, mu) = byte_to_bcd2(date.month as u8); + let yr = date.year as u16; + let yr_offset = (yr - 1970_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + rtc.dr.write(|w| unsafe { + w.dt() + .bits(dt) + .du() + .bits(du) + .mt() + .bit(mt > 0) + .mu() + .bits(mu) + .yt() + .bits(yt) + .yu() + .bits(yu) + .wdu() + .bits(date.day as u8) + }); +} + +fn byte_to_bcd2(byte: u8) -> (u8, u8) { + let mut bcd_high: u8 = 0; + let mut value = byte; + + while value >= 10 { + bcd_high += 1; + value -= 10; + } + + (bcd_high, ((bcd_high << 4) | value) as u8) +} + +fn bcd2_to_byte(bcd: (u8, u8)) -> u8 { + let value = bcd.1 | bcd.0 << 4; + + let tmp = ((value & 0xF0) >> 0x4) * 10; + + tmp + (value & 0x0F) +} diff --git a/stm32l4xx-hal/src/rtc/rtc2.rs b/stm32l4xx-hal/src/rtc/rtc2.rs new file mode 100644 index 0000000..a30cf57 --- /dev/null +++ b/stm32l4xx-hal/src/rtc/rtc2.rs @@ -0,0 +1,117 @@ +use crate::pac::RTC; + +pub fn reset_gpio(rtc: &RTC) { + rtc.or + .modify(|_, w| w.rtc_alarm_type().clear_bit().rtc_out_rmp().clear_bit()); +} + +/// true if initf bit indicates RTC peripheral is in init mode +pub fn is_init_mode(rtc: &RTC) -> bool { + rtc.isr.read().initf().bit_is_set() +} + +/// to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode +pub fn enter_init_mode(rtc: &RTC) { + rtc.isr.modify(|_, w| w.init().set_bit()); +} + +/// counting will restart in 4 RTCCLK cycles +pub fn exit_init_mode(rtc: &RTC) { + rtc.isr.modify(|_, w| w.init().clear_bit()); // Exits init mode +} + +/// has wakeup timer expired? +pub fn is_wakeup_timer_flag_set(rtc: &RTC) -> bool { + rtc.isr.read().wutf().bit_is_set() +} + +pub fn is_wakeup_timer_write_flag_set(rtc: &RTC) -> bool { + rtc.isr.read().wutwf().bit_is_set() +} + +/// clear the wakeup timer flag +pub fn clear_wakeup_timer_flag(rtc: &RTC) { + rtc.isr.modify(|_, w| w.wutf().clear_bit()); +} + +/// has alarm A been triggered +pub fn is_alarm_a_flag_set(rtc: &RTC) -> bool { + rtc.isr.read().alraf().bit_is_set() +} + +/// clear the alarm A flag +pub fn clear_alarm_a_flag(rtc: &RTC) { + rtc.isr.modify(|_, w| w.alraf().clear_bit()); +} + +/// has alarm B been triggered? +pub fn is_alarm_b_flag_set(rtc: &RTC) -> bool { + rtc.isr.read().alrbf().bit_is_set() +} + +/// clear the alarm B flag +pub fn clear_alarm_b_flag(rtc: &RTC) { + rtc.isr.modify(|_, w| w.alrbf().clear_bit()); +} + +/// has timestamp event triggered +pub fn is_timestamp_flag_set(rtc: &RTC) -> bool { + rtc.isr.read().tsf().bit_is_set() +} + +/// clear the timestamp event flag +pub fn clear_timestamp_flag(rtc: &RTC) { + rtc.isr.modify(|_, w| w.tsf().clear_bit()); +} + +pub fn is_alarm_a_accessible(rtc: &RTC) -> bool { + rtc.isr.read().alrawf().bit_is_set() +} + +pub fn is_alarm_b_accessible(rtc: &RTC) -> bool { + rtc.isr.read().alrbwf().bit_is_set() +} + +// AN7459 +// L4 series except L41/2 has 20 backup registers +// L41/2, L4P/Q and L4R/S have 32 backup registers +#[cfg(not(any( + feature = "stm32l4r5", + feature = "stm32l4s5", + feature = "stm32l4r7", + feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +)))] +pub const BACKUP_REGISTER_COUNT: usize = 20; +#[cfg(any( + feature = "stm32l4r5", + feature = "stm32l4s5", + feature = "stm32l4r7", + feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9" +))] +pub const BACKUP_REGISTER_COUNT: usize = 32; + +/// Read content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn read_backup_register(rtc: &RTC, register: usize) -> Option { + if register < BACKUP_REGISTER_COUNT { + Some(rtc.bkpr[register].read().bits()) + } else { + None + } +} + +/// Set content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn write_backup_register(rtc: &RTC, register: usize, value: u32) { + if register < BACKUP_REGISTER_COUNT { + unsafe { rtc.bkpr[register].write(|w| w.bits(value)) } + } +} diff --git a/stm32l4xx-hal/src/rtc/rtc3.rs b/stm32l4xx-hal/src/rtc/rtc3.rs new file mode 100644 index 0000000..7259fef --- /dev/null +++ b/stm32l4xx-hal/src/rtc/rtc3.rs @@ -0,0 +1,111 @@ +use crate::pac::RTC; + +pub fn reset_gpio(rtc: &RTC) { + rtc.cr.modify(|_, w| { + w.out2en() + .clear_bit() + .tampalrm_type() + .clear_bit() + .tampalrm_pu() + .clear_bit() + }); +} + +/// true if initf bit indicates RTC peripheral is in init mode +pub fn is_init_mode(rtc: &RTC) -> bool { + rtc.icsr.read().initf().bit_is_set() +} + +/// to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode +pub fn enter_init_mode(rtc: &RTC) { + rtc.icsr.modify(|_, w| w.init().set_bit()); +} + +/// counting will restart in 4 RTCCLK cycles +pub fn exit_init_mode(rtc: &RTC) { + rtc.icsr.modify(|_, w| w.init().clear_bit()); // Exits init mode +} + +/// has wakeup timer expired? +pub fn is_wakeup_timer_flag_set(rtc: &RTC) -> bool { + rtc.sr.read().wutf().bit_is_set() +} + +/// are WUT settings modifiable +pub fn is_wakeup_timer_write_flag_set(rtc: &RTC) -> bool { + rtc.icsr.read().wutwf().bit_is_set() +} + +/// clear the wakeup timer flag +pub fn clear_wakeup_timer_flag(rtc: &RTC) { + rtc.scr.write(|w| w.cwutf().set_bit()); +} + +/// has alarm A been triggered +pub fn is_alarm_a_flag_set(rtc: &RTC) -> bool { + rtc.sr.read().alraf().bit_is_set() +} + +/// clear the alarm A flag +pub fn clear_alarm_a_flag(rtc: &RTC) { + rtc.scr.write(|w| w.calraf().set_bit()); +} + +/// has alarm B been triggered? +pub fn is_alarm_b_flag_set(rtc: &RTC) -> bool { + rtc.sr.read().alrbf().bit_is_set() +} + +/// clear the alarm B flag +pub fn clear_alarm_b_flag(rtc: &RTC) { + rtc.scr.write(|w| w.calrbf().set_bit()); +} + +/// has timestamp event triggered +pub fn is_timestamp_flag_set(rtc: &RTC) -> bool { + rtc.sr.read().tsf().bit_is_set() +} + +/// clear the timestamp event flag +pub fn clear_timestamp_flag(rtc: &RTC) { + rtc.scr.write(|w| w.ctsf().set_bit()); +} + +pub fn is_alarm_a_accessible(_rtc: &RTC) -> bool { + // RTC type 3 has no wait after disabling the alarm (AN4759 - Rev 7, Table 8) + true +} + +pub fn is_alarm_b_accessible(_rtc: &RTC) -> bool { + // RTC type 3 has no wait after disabling the alarm (AN4759 - Rev 7, Table 8) + true +} + +// AN7459 +// L4 series except L41/2 has 20 backup registers +// L41/2, L4P/Q and L4R/S have 32 backup registers +pub const BACKUP_REGISTER_COUNT: usize = 32; + +/// Read content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn read_backup_register(_rtc: &RTC, register: usize) -> Option { + if register < BACKUP_REGISTER_COUNT { + //Some(rtc.bkpr[register].read().bits()) + None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not even in the L412 PAC + } else { + None + } +} + +/// Set content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn write_backup_register(_rtc: &RTC, register: usize, _value: u32) { + if register < BACKUP_REGISTER_COUNT { + // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not even in the L412 PAC + //unsafe { self.rtc.bkpr[register].write(|w| w.bits(value)) } + } +} diff --git a/stm32l4xx-hal/src/serial.rs b/stm32l4xx-hal/src/serial.rs new file mode 100644 index 0000000..2e36c7b --- /dev/null +++ b/stm32l4xx-hal/src/serial.rs @@ -0,0 +1,1155 @@ +//! Serial module +//! +//! This module support both polling and interrupt based accesses to the serial peripherals. + +use core::fmt; +use core::marker::PhantomData; +use core::ops::DerefMut; +use core::ptr; +use core::sync::atomic::{self, Ordering}; +use embedded_dma::StaticWriteBuffer; +use stable_deref_trait::StableDeref; + +use crate::hal::serial::{self, Write}; + +use crate::dma::{ + dma1, CircBuffer, DMAFrame, FrameReader, FrameSender, Receive, RxDma, TransferPayload, + Transmit, TxDma, +}; +use crate::dmamux::{DmaInput, DmaMux}; +use crate::gpio::{self, Alternate, OpenDrain, PushPull}; +use crate::pac; +use crate::rcc::{Clocks, Enable, RccBus, Reset}; +use crate::time::{Bps, U32Ext}; + +#[cfg(any( + //feature = "stm32l451", // missing PAC support + // feature = "stm32l452", // missing PAC support + // feature = "stm32l462", // missing PAC support + // feature = "stm32l471", // missing PAC support + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +use crate::dma::dma2; + +/// Interrupt event +pub enum Event { + /// New data has been received + Rxne, + /// New data can be sent + Txe, + /// The line has gone idle + Idle, + /// Character match + CharacterMatch, + /// Receiver timeout + ReceiverTimeout, +} + +/// Serial error +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// Framing error + Framing, + /// Noise error + Noise, + /// RX buffer overrun + Overrun, + /// Parity check error + Parity, +} + +/// USART parity settings +pub enum Parity { + /// No parity + ParityNone, + /// Even parity + ParityEven, + /// Odd parity + ParityOdd, +} + +/// USART stopbits settings +pub enum StopBits { + /// 1 stop bit + STOP1, + /// 0.5 stop bits + STOP0P5, + /// 2 stop bits + STOP2, + // 1.5 stop bits + STOP1P5, +} + +/// USART oversampling settings +pub enum Oversampling { + /// Oversample 8 times (allows for faster data rates) + Over8, + /// Oversample 16 times (higher stability) + Over16, +} + +/// USART Configuration structure +pub struct Config { + baudrate: Bps, + parity: Parity, + stopbits: StopBits, + oversampling: Oversampling, + character_match: Option, + receiver_timeout: Option, + disable_overrun: bool, + onebit_sampling: bool, +} + +impl Config { + /// Set the baudrate to a specific value + pub fn baudrate(mut self, baudrate: Bps) -> Self { + self.baudrate = baudrate; + self + } + + /// Set parity to none + pub fn parity_none(mut self) -> Self { + self.parity = Parity::ParityNone; + self + } + + /// Set parity to even + pub fn parity_even(mut self) -> Self { + self.parity = Parity::ParityEven; + self + } + + /// Set parity to odd + pub fn parity_odd(mut self) -> Self { + self.parity = Parity::ParityOdd; + self + } + + /// Set the number of stopbits + pub fn stopbits(mut self, stopbits: StopBits) -> Self { + self.stopbits = stopbits; + self + } + + /// Set the oversampling size + pub fn oversampling(mut self, oversampling: Oversampling) -> Self { + self.oversampling = oversampling; + self + } + + /// Set the character match character + pub fn character_match(mut self, character_match: u8) -> Self { + self.character_match = Some(character_match); + self + } + + /// Set the receiver timeout, the value is the number of bit durations + /// + /// Note that it only takes 24 bits, using more than this will cause a panic. + pub fn receiver_timeout(mut self, receiver_timeout: u32) -> Self { + assert!(receiver_timeout < 1 << 24); + self.receiver_timeout = Some(receiver_timeout); + self + } + + /// Disable overrun detection + pub fn with_overrun_disabled(mut self) -> Self { + self.disable_overrun = true; + self + } + + /// Change to onebit sampling + pub fn with_onebit_sampling(mut self) -> Self { + self.onebit_sampling = true; + self + } +} + +impl Default for Config { + fn default() -> Config { + let baudrate = 115_200_u32.bps(); + Config { + baudrate, + parity: Parity::ParityNone, + stopbits: StopBits::STOP1, + oversampling: Oversampling::Over16, + character_match: None, + receiver_timeout: None, + disable_overrun: false, + onebit_sampling: false, + } + } +} + +impl From for Config { + fn from(baudrate: Bps) -> Config { + Config { + baudrate, + ..Default::default() + } + } +} + +/// Serial abstraction +pub struct Serial { + usart: USART, + pins: PINS, +} + +/// Serial receiver +pub struct Rx { + _usart: PhantomData, +} + +/// Serial transmitter +pub struct Tx { + _usart: PhantomData, +} + +macro_rules! hal { + ($( + $(#[$meta:meta])* + $USARTX:ident: ( + $usartX:ident, + $pclkX:ident, + tx: ($txdma:ident, $dmatxch:path, $dmatxsel:path), + rx: ($rxdma:ident, $dmarxch:path, $dmarxsel:path) + ), + )+) => { + $( + impl Serial { + /// Configures the serial interface and creates the interface + /// struct. + /// + /// `Config` is a config struct that configures baud rate, stop bits and parity. + /// + /// `Clocks` passes information about the current frequencies of + /// the clocks. The existence of the struct ensures that the + /// clock settings are fixed. + /// + /// The `serial` struct takes ownership over the `USARTX` device + /// registers and the specified `PINS` + /// + /// `MAPR` and `APBX` are register handles which are passed for + /// configuration. (`MAPR` is used to map the USART to the + /// corresponding pins. `APBX` is used to reset the USART.) + pub fn $usartX( + usart: pac::$USARTX, + pins: PINS, + config: impl Into, + clocks: Clocks, + apb: &mut ::Bus, + ) -> Self + where + PINS: Pins, + { + let config = config.into(); + + // enable or reset $USARTX + ::enable(apb); + ::reset(apb); + + // Reset other registers to disable advanced USART features + usart.cr1.reset(); + usart.cr2.reset(); + usart.cr3.reset(); + + // Configure baud rate + match config.oversampling { + Oversampling::Over8 => { + let uartdiv = 2 * clocks.$pclkX().raw() / config.baudrate.0; + assert!(uartdiv >= 16, "impossible baud rate"); + + let lower = (uartdiv & 0xf) >> 1; + let brr = (uartdiv & !0xf) | lower; + + usart.cr1.modify(|_, w| w.over8().set_bit()); + usart.brr.write(|w| unsafe { w.bits(brr) }); + } + Oversampling::Over16 => { + let brr = clocks.$pclkX().raw() / config.baudrate.0; + assert!(brr >= 16, "impossible baud rate"); + + usart.brr.write(|w| unsafe { w.bits(brr) }); + } + } + + if let Some(val) = config.receiver_timeout { + usart.rtor.modify(|_, w| w.rto().bits(val)); + } + + // enable DMA transfers + usart.cr3.modify(|_, w| w.dmat().set_bit().dmar().set_bit()); + + // Configure hardware flow control (CTS/RTS or RS485 Driver Enable) + if PINS::FLOWCTL { + usart.cr3.modify(|_, w| w.rtse().set_bit().ctse().set_bit()); + } else if PINS::DEM { + usart.cr3.modify(|_, w| w.dem().set_bit()); + + // Pre/post driver enable set conservative to the max time + usart.cr1.modify(|_, w| w.deat().bits(0b1111).dedt().bits(0b1111)); + } else { + usart.cr3.modify(|_, w| w.rtse().clear_bit().ctse().clear_bit()); + } + + // Enable One bit sampling method + usart.cr3.modify(|_, w| { + if config.onebit_sampling { + w.onebit().set_bit(); + } + + if config.disable_overrun { + w.ovrdis().set_bit(); + } + + // configure Half Duplex + if PINS::HALF_DUPLEX { + w.hdsel().set_bit(); + } + + w + }); + + // Configure parity and word length + // Unlike most uart devices, the "word length" of this usart device refers to + // the size of the data plus the parity bit. I.e. "word length"=8, parity=even + // results in 7 bits of data. Therefore, in order to get 8 bits and one parity + // bit, we need to set the "word" length to 9 when using parity bits. + let (word_length, parity_control_enable, parity) = match config.parity { + Parity::ParityNone => (false, false, false), + Parity::ParityEven => (true, true, false), + Parity::ParityOdd => (true, true, true), + }; + usart.cr1.modify(|_r, w| { + w + .m0().bit(word_length) + .ps().bit(parity) + .pce().bit(parity_control_enable) + }); + + // Configure stop bits + let stop_bits = match config.stopbits { + StopBits::STOP1 => 0b00, + StopBits::STOP0P5 => 0b01, + StopBits::STOP2 => 0b10, + StopBits::STOP1P5 => 0b11, + }; + usart.cr2.modify(|_r, w| { + w.stop().bits(stop_bits); + + // Setup character match (if requested) + if let Some(c) = config.character_match { + w.add().bits(c); + } + + if config.receiver_timeout.is_some() { + w.rtoen().set_bit(); + } + + w + }); + + + // UE: enable USART + // RE: enable receiver + // TE: enable transceiver + usart + .cr1 + .modify(|_, w| w.ue().set_bit().re().set_bit().te().set_bit()); + + Serial { usart, pins } + } + + /// Starts listening for an interrupt event + pub fn listen(&mut self, event: Event) { + match event { + Event::Rxne => { + self.usart.cr1.modify(|_, w| w.rxneie().set_bit()) + }, + Event::Txe => { + self.usart.cr1.modify(|_, w| w.txeie().set_bit()) + }, + Event::Idle => { + self.usart.cr1.modify(|_, w| w.idleie().set_bit()) + }, + Event::CharacterMatch => { + self.usart.cr1.modify(|_, w| w.cmie().set_bit()) + }, + Event::ReceiverTimeout => { + self.usart.cr1.modify(|_, w| w.rtoie().set_bit()) + }, + } + } + + /// Check for, and return, any errors + /// + /// See [`Rx::check_for_error`]. + pub fn check_for_error() -> Result<(), Error> { + let mut rx: Rx = Rx { + _usart: PhantomData, + }; + rx.check_for_error() + } + + /// Stops listening for an interrupt event + pub fn unlisten(&mut self, event: Event) { + match event { + Event::Rxne => { + self.usart.cr1.modify(|_, w| w.rxneie().clear_bit()) + }, + Event::Txe => { + self.usart.cr1.modify(|_, w| w.txeie().clear_bit()) + }, + Event::Idle => { + self.usart.cr1.modify(|_, w| w.idleie().clear_bit()) + }, + Event::CharacterMatch => { + self.usart.cr1.modify(|_, w| w.cmie().clear_bit()) + }, + Event::ReceiverTimeout => { + self.usart.cr1.modify(|_, w| w.rtoie().clear_bit()) + }, + } + } + + /// Splits the `Serial` abstraction into a transmitter and a receiver half + pub fn split(self) -> (Tx, Rx) { + ( + Tx { + _usart: PhantomData, + }, + Rx { + _usart: PhantomData, + }, + ) + } + + /// Frees the USART peripheral + pub fn release(self) -> (pac::$USARTX, PINS) { + (self.usart, self.pins) + } + } + + impl serial::Read for Serial { + type Error = Error; + + fn read(&mut self) -> nb::Result { + let mut rx: Rx = Rx { + _usart: PhantomData, + }; + rx.read() + } + } + + impl serial::Read for Rx { + type Error = Error; + + fn read(&mut self) -> nb::Result { + self.check_for_error()?; + + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*pac::$USARTX::ptr()).isr.read() }; + + if isr.rxne().bit_is_set() { + // NOTE(read_volatile) see `write_volatile` below + return Ok(unsafe { + ptr::read_volatile(&(*pac::$USARTX::ptr()).rdr as *const _ as *const _) + }); + } + + Err(nb::Error::WouldBlock) + } + } + + impl serial::Write for Serial { + type Error = Error; + + fn flush(&mut self) -> nb::Result<(), Error> { + let mut tx: Tx = Tx { + _usart: PhantomData, + }; + tx.flush() + } + + fn write(&mut self, byte: u8) -> nb::Result<(), Error> { + let mut tx: Tx = Tx { + _usart: PhantomData, + }; + tx.write(byte) + } + } + + impl serial::Write for Tx { + // NOTE(Void) See section "29.7 USART interrupts"; the only possible errors during + // transmission are: clear to send (which is disabled in this case) errors and + // framing errors (which only occur in SmartCard mode); neither of these apply to + // our hardware configuration + type Error = Error; + + fn flush(&mut self) -> nb::Result<(), Error> { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*pac::$USARTX::ptr()).isr.read() }; + + if isr.tc().bit_is_set() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn write(&mut self, byte: u8) -> nb::Result<(), Error> { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*pac::$USARTX::ptr()).isr.read() }; + + if isr.txe().bit_is_set() { + // NOTE(unsafe) atomic write to stateless register + // NOTE(write_volatile) 8-bit write that's not possible through the svd2rust API + unsafe { + ptr::write_volatile(&(*pac::$USARTX::ptr()).tdr as *const _ as *mut _, byte) + } + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + } + + impl embedded_hal::blocking::serial::write::Default + for Tx {} + + pub type $rxdma = RxDma, $dmarxch>; + pub type $txdma = TxDma, $dmatxch>; + + impl Receive for $rxdma { + type RxChannel = $dmarxch; + type TransmittedWord = u8; + } + + impl Transmit for $txdma { + type TxChannel = $dmatxch; + type ReceivedWord = u8; + } + + impl TransferPayload for $rxdma { + fn start(&mut self) { + self.channel.start(); + } + fn stop(&mut self) { + self.channel.stop(); + } + } + + impl TransferPayload for $txdma { + fn start(&mut self) { + self.channel.start(); + } + fn stop(&mut self) { + self.channel.stop(); + } + } + + impl Rx { + pub fn with_dma(self, channel: $dmarxch) -> $rxdma { + RxDma { + payload: self, + channel, + } + } + + /// Check for, and return, any errors + /// + /// The `read` methods can only return one error at a time, but + /// there might actually be multiple errors. This method will + /// return and clear a currently active error. Once it returns + /// `Ok(())`, it should be possible to proceed with the next + /// `read` call unimpeded. + pub fn check_for_error(&mut self) -> Result<(), Error> { + // NOTE(unsafe): Only used for atomic access. + let isr = unsafe { (*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.pe().bit_is_set() { + icr.write(|w| w.pecf().clear()); + return Err(Error::Parity); + } + if isr.fe().bit_is_set() { + icr.write(|w| w.fecf().clear()); + return Err(Error::Framing); + } + if isr.nf().bit_is_set() { + icr.write(|w| w.ncf().clear()); + return Err(Error::Noise); + } + if isr.ore().bit_is_set() { + icr.write(|w| w.orecf().clear()); + return Err(Error::Overrun); + } + + Ok(()) + } + + /// Checks to see if the USART peripheral has detected an idle line and clears + /// the flag + pub fn is_idle(&mut self, clear: bool) -> bool { + let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.idle().bit_is_set() { + if clear { + icr.write(|w| w.idlecf().set_bit() ); + } + true + } else { + false + } + } + + + /// Checks to see if the USART peripheral has detected an receiver timeout and + /// clears the flag + pub fn is_receiver_timeout(&mut self, clear: bool) -> bool { + let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.rtof().bit_is_set() { + if clear { + icr.write(|w| w.rtocf().set_bit() ); + } + true + } else { + false + } + } + + /// Checks to see if the USART peripheral has detected an character match and + /// clears the flag + pub fn check_character_match(&mut self, clear: bool) -> bool { + let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.cmf().bit_is_set() { + if clear { + icr.write(|w| w.cmcf().set_bit() ); + } + true + } else { + false + } + } + } + + impl crate::dma::CharacterMatch for Rx { + /// Checks to see if the USART peripheral has detected an character match and + /// clears the flag + fn check_character_match(&mut self, clear: bool) -> bool { + self.check_character_match(clear) + } + } + + impl crate::dma::ReceiverTimeout for Rx { + fn check_receiver_timeout(&mut self, clear: bool) -> bool { + self.is_receiver_timeout(clear) + } + } + + impl crate::dma::OperationError<(), Error> for Rx{ + fn check_operation_error(&mut self) -> Result<(), Error> { + self.check_for_error() + } + } + + impl Tx { + pub fn with_dma(self, channel: $dmatxch) -> $txdma { + TxDma { + payload: self, + channel, + } + } + } + + impl $rxdma { + pub fn split(mut self) -> (Rx, $dmarxch) { + self.stop(); + let RxDma {payload, channel} = self; + ( + payload, + channel + ) + } + } + + impl $txdma { + pub fn split(mut self) -> (Tx, $dmatxch) { + self.stop(); + let TxDma {payload, channel} = self; + ( + payload, + channel, + ) + } + } + + impl crate::dma::CircReadDma for $rxdma + where + &'static mut B: StaticWriteBuffer, + B: 'static, + Self: core::marker::Sized, + { + fn circ_read(mut self, mut buffer: &'static mut B, + ) -> CircBuffer + { + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + self.channel.set_peripheral_address( + unsafe { &(*pac::$USARTX::ptr()).rdr as *const _ as u32 }, + false, + ); + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); + + // Tell DMA to request from serial + self.channel.set_request_line($dmarxsel).unwrap(); + + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .set_bit() + // write to memory + .dir() + .clear_bit() + }); + + // NOTE(compiler_fence) operations on `buffer` should not be reordered after + // the next statement, which starts the DMA transfer + atomic::compiler_fence(Ordering::Release); + + self.start(); + + CircBuffer::new(buffer, self) + } + } + + impl $rxdma { + /// Create a frame reader that can either react on the Character match interrupt or + /// Transfer Complete from the DMA. + pub fn frame_reader( + mut self, + buffer: BUFFER, + ) -> FrameReader + where + BUFFER: Sized + StableDeref> + DerefMut + 'static, + { + let usart = unsafe{ &(*pac::$USARTX::ptr()) }; + + // Setup DMA transfer + let buf = &*buffer; + self.channel.set_peripheral_address(&usart.rdr as *const _ as u32, false); + self.channel.set_memory_address(unsafe { buf.buffer_address_for_dma() } as u32, true); + self.channel.set_transfer_length(buf.max_len() as u16); + + // Tell DMA to request from serial + self.channel.set_request_line($dmarxsel).unwrap(); + + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // Peripheral -> Mem + .dir() + .clear_bit() + }); + + // NOTE(compiler_fence) operations on `buffer` should not be reordered after + // the next statement, which starts the DMA transfer + atomic::compiler_fence(Ordering::Release); + + self.channel.start(); + + FrameReader::new(buffer, self, usart.cr2.read().add().bits()) + } + } + + impl $txdma { + /// Creates a new DMA frame sender + pub fn frame_sender( + mut self, + ) -> FrameSender + where + BUFFER: Sized + StableDeref> + DerefMut + 'static, + { + let usart = unsafe{ &(*pac::$USARTX::ptr()) }; + + // Setup DMA + self.channel.set_peripheral_address(&usart.tdr as *const _ as u32, false); + + // Tell DMA to request from serial + self.channel.set_request_line($dmatxsel).unwrap(); + + self.channel.ccr().modify(|_, w| unsafe { + w.mem2mem() + .clear_bit() + // 00: Low, 01: Medium, 10: High, 11: Very high + .pl() + .bits(0b01) + // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .msize() + .bits(0b00) + // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .psize() + .bits(0b00) + // Mem -> Peripheral + .dir() + .set_bit() + }); + + FrameSender::new(self) + } + } + )+ + } +} + +hal! { + USART1: (usart1, pclk2, tx: (TxDma1, dma1::C4, DmaInput::Usart1Tx), rx: (RxDma1, dma1::C5, DmaInput::Usart1Rx)), + USART2: (usart2, pclk1, tx: (TxDma2, dma1::C7, DmaInput::Usart2Tx), rx: (RxDma2, dma1::C6, DmaInput::Usart2Rx)), +} + +#[cfg(not(any(feature = "stm32l432", feature = "stm32l442")))] +hal! { + USART3: (usart3, pclk1, tx: (TxDma3, dma1::C2, DmaInput::Usart3Tx), rx: (RxDma3, dma1::C3, DmaInput::Usart3Rx)), +} + +#[cfg(any( + // feature = "stm32l451", // missing PAC support + // feature = "stm32l452", // missing PAC support + // feature = "stm32l462", // missing PAC support + // feature = "stm32l471", // missing PAC support + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +hal! { + UART4: (uart4, pclk1, tx: (TxDma4, dma2::C3, DmaInput::Uart4Tx), rx: (RxDma4, dma2::C5, DmaInput::Uart4Rx)), +} + +#[cfg(any( + // feature = "stm32l471", // missing PAC support + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +hal! { + UART5: (uart5, pclk1, tx: (TxDma5, dma2::C1, DmaInput::Uart5Tx), rx: (RxDma5, dma2::C2, DmaInput::Uart5Rx)), +} + +impl fmt::Write for Serial +where + Serial: crate::hal::serial::Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + let _ = s + .as_bytes() + .iter() + .map(|c| nb::block!(self.write(*c))) + .last(); + Ok(()) + } +} + +impl fmt::Write for Tx +where + Tx: crate::hal::serial::Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + let _ = s + .as_bytes() + .iter() + .map(|c| nb::block!(self.write(*c))) + .last(); + Ok(()) + } +} + +/// Marks pins as being as being TX pins for the given USART instance +pub trait TxPin: private::SealedTx {} + +/// Marks pins as being TX Half Duplex pins for the given USART instance +pub trait TxHalfDuplexPin: private::SealedTxHalfDuplex {} + +/// Marks pins as being as being RX pins for the given USART instance +pub trait RxPin: private::SealedRx {} + +/// Marks pins as being as being RTS pins for the given USART instance +pub trait RtsDePin: private::SealedRtsDe {} + +/// Marks pins as being as being CTS pins for the given USART instance +pub trait CtsPin: private::SealedCts {} + +macro_rules! impl_pin_traits { + ( + $( + $instance:ident: { + $( + $af:literal: { + TX: $($tx:ident),*; + RX: $($rx:ident),*; + RTS_DE: $($rts_de:ident),*; + CTS: $($cts:ident),*; + } + )* + } + )* + ) => { + $( + $( + $( + impl private::SealedTx for + gpio::$tx> {} + impl TxPin for + gpio::$tx> {} + )* + + $( + impl private::SealedTxHalfDuplex for + gpio::$tx> {} + impl TxHalfDuplexPin for + gpio::$tx> {} + )* + + $( + impl private::SealedRx for + gpio::$rx> {} + impl RxPin for + gpio::$rx> {} + )* + + $( + impl private::SealedRtsDe for + gpio::$rts_de> {} + impl RtsDePin for + gpio::$rts_de> {} + )* + + $( + impl private::SealedCts for + gpio::$cts> {} + impl CtsPin for + gpio::$cts> {} + )* + )* + )* + }; +} + +impl_pin_traits! { + USART1: { + 7: { + TX: PA9, PB6; + RX: PA10, PB7; + RTS_DE: PA12, PB3; + CTS: PA11, PB4; + } + } + USART2: { + 7: { + TX: PA2, PD5; + RX: PA3, PD6; + RTS_DE: PA1, PD4; + CTS: PA0, PD3; + } + 3: { + TX: ; + RX: PA15; + RTS_DE: ; + CTS: ; + } + } + USART3: { + 7: { + TX: PB10, PC4, PC10, PD8; + RX: PB11, PC5, PC11, PD9; + RTS_DE: PB1, PB14, PD2, PD12; + CTS: PA6, PB13, PD11; + } + } +} + +#[cfg(any( + // feature = "stm32l451", + // feature = "stm32l452", + // feature = "stm32l462", + // feature = "stm32l471", + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +impl_pin_traits! { + UART4: { + 8: { + TX: PA0, PC10; + RX: PA1, PC11; + RTS_DE: PA15; + CTS: PB7; + } + } +} + +#[cfg(any( + // feature = "stm32l471", ,, missing PAC support + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +impl_pin_traits! { + UART5: { + 8: { + TX: PC12; + RX: PD2; + RTS_DE: PB4; + CTS: PB5; + } + } +} + +/// Pins trait for detecting hardware flow control or RS485 mode. +pub trait Pins { + const FLOWCTL: bool; + const DEM: bool; + const HALF_DUPLEX: bool; +} + +// No flow control, just Rx+Tx +impl Pins for (Tx, Rx) +where + Tx: TxPin, + Rx: RxPin, +{ + const FLOWCTL: bool = false; + const DEM: bool = false; + const HALF_DUPLEX: bool = false; +} + +// No flow control Half_duplex, just Tx +impl Pins for (Tx,) +where + Tx: TxHalfDuplexPin, +{ + const FLOWCTL: bool = false; + const DEM: bool = false; + const HALF_DUPLEX: bool = true; +} + +// Hardware flow control, Rx+Tx+Rts+Cts +impl Pins for (Tx, Rx, Rts, Cts) +where + Tx: TxPin, + Rx: RxPin, + Rts: RtsDePin, + Cts: CtsPin, +{ + const FLOWCTL: bool = true; + const DEM: bool = false; + const HALF_DUPLEX: bool = false; +} + +// DEM for RS485 mode +impl Pins for (Tx, Rx, De) +where + Tx: TxPin, + Rx: RxPin, + De: RtsDePin, +{ + const FLOWCTL: bool = false; + const DEM: bool = true; + const HALF_DUPLEX: bool = false; +} + +/// Contains supertraits used to restrict which traits users can implement +mod private { + pub trait SealedTx {} + pub trait SealedTxHalfDuplex {} + pub trait SealedRx {} + pub trait SealedRtsDe {} + pub trait SealedCts {} +} diff --git a/stm32l4xx-hal/src/signature.rs b/stm32l4xx-hal/src/signature.rs new file mode 100644 index 0000000..5e94ebf --- /dev/null +++ b/stm32l4xx-hal/src/signature.rs @@ -0,0 +1,143 @@ +//! Device electronic signature +//! +//! (stored in flash memory) +//! +//! Based on the STM32F4xx HAL. + +use core::str::from_utf8_unchecked; + +/// This is the test voltage, in millivolts of the calibration done at the factory +pub const VDDA_CALIB_MV: u32 = 3000; + +macro_rules! define_ptr_type { + ($name: ident, $ptr: expr) => { + impl $name { + fn ptr() -> *const Self { + $ptr as *const _ + } + + /// Returns a wrapped reference to the value in flash memory + pub fn get() -> &'static Self { + unsafe { &*Self::ptr() } + } + } + }; +} + +/// Uniqure Device ID register +#[derive(Hash, Debug)] +#[repr(C)] +pub struct Uid { + x: u16, + y: u16, + waf_lot: [u8; 8], +} +define_ptr_type!(Uid, 0x1FFF_7590); + +impl Uid { + /// X coordinate on wafer + pub fn x(&self) -> u16 { + self.x + } + + /// Y coordinate on wafer + pub fn y(&self) -> u16 { + self.y + } + + /// Wafer number + pub fn waf_num(&self) -> u8 { + self.waf_lot[0] + } + + /// Lot number + pub fn lot_num(&self) -> &str { + unsafe { from_utf8_unchecked(&self.waf_lot[1..]) } + } + + /// As a byte array + pub fn as_bytes() -> &'static [u8; 12] { + unsafe { &*(Self::ptr() as *const _) } + } +} + +/// Size of integrated flash +#[derive(Debug)] +#[repr(C)] +pub struct FlashSize(u16); +define_ptr_type!(FlashSize, 0x1FFF_75E0); + +impl FlashSize { + /// Read flash size in kilobytes + pub fn kilo_bytes(&self) -> u16 { + self.0 + } + + /// Read flash size in bytes + pub fn bytes(&self) -> usize { + usize::from(self.kilo_bytes()) * 1024 + } +} + +/// ADC VREF calibration value is stored in at the factory +#[derive(Debug)] +#[repr(C)] +pub struct VrefCal(u16); +define_ptr_type!(VrefCal, 0x1FFF_75AA); + +impl VrefCal { + /// Read calibration value + pub fn read(&self) -> u16 { + self.0 + } +} + +/// A temperature reading taken at 30°C stored at the factory +/// aka TS_CAL1 in reference manual +#[derive(Debug)] +#[repr(C)] +pub struct VtempCalLow(u16); +define_ptr_type!(VtempCalLow, 0x1FFF_75A8); + +impl VtempCalLow { + /// aka TS_CAL1_TEMP in reference manual + pub const TEMP_DEGREES: u16 = 30; + /// Read calibration value + pub fn read(&self) -> u16 { + self.0 + } +} + +/// A temperature reading taken at 130°C stored at the factory +/// aka TS_CAL2 in reference manual +#[derive(Debug)] +#[repr(C)] +pub struct VtempCalHigh(u16); +define_ptr_type!(VtempCalHigh, 0x1FFF_75CA); + +impl VtempCalHigh { + /// aka TS_CAL2_TEMP in reference manual + /// Feature gate Required: this is 110 for L47x/L48x, 130 for other L4s according to + /// https://github.com/STMicroelectronics/STM32CubeL4/blob/5e1553e07706491bd11f4edd304e093b6e4b83a4/Drivers/STM32L4xx_HAL_Driver/Inc/stm32l4xx_ll_adc.h#L352-L356 + + // L47/L48 + #[cfg(any( + feature = "stm32l471", + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l486" + ))] + pub const TEMP_DEGREES: u16 = 110; + // else + #[cfg(not(any( + feature = "stm32l471", + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l486" + )))] + pub const TEMP_DEGREES: u16 = 130; + /// Read calibration value + pub fn read(&self) -> u16 { + self.0 + } +} diff --git a/stm32l4xx-hal/src/spi.rs b/stm32l4xx-hal/src/spi.rs new file mode 100644 index 0000000..a79b886 --- /dev/null +++ b/stm32l4xx-hal/src/spi.rs @@ -0,0 +1,790 @@ +//! Serial Peripheral Interface (SPI) bus +//! +//! The PACs and SVDs are not set up granularity enough to handle all peripheral configurations. +//! SPI2 is enabled for stm32l4x2 feature at a HAL level even though some variants do and some +//! don't have it (L432xx and L442xx don't, L452xx does). Users of this MCU variant that +//! don't have it shouldn't attempt to use it. Relevant info is on user-manual level. + +use core::ptr; +use core::sync::atomic; +use core::sync::atomic::Ordering; + +#[cfg(not(any(feature = "stm32l433", feature = "stm32l443",)))] +use crate::dma::dma2; +use crate::dma::{self, dma1, TransferPayload}; +use crate::dmamux::{DmaInput, DmaMux}; +use crate::gpio::{Alternate, PushPull}; +use crate::hal::spi::{FullDuplex, Mode, Phase, Polarity}; +use crate::rcc::{Clocks, Enable, RccBus, Reset}; +use crate::time::Hertz; + +use embedded_dma::{StaticReadBuffer, StaticWriteBuffer}; + +/// SPI error +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// Overrun occurred + Overrun, + /// Mode fault occurred + ModeFault, + /// CRC error + Crc, +} + +#[doc(hidden)] +mod private { + pub trait Sealed {} +} + +/// SCK pin. This trait is sealed and cannot be implemented. +pub trait SckPin: private::Sealed {} +/// MISO pin. This trait is sealed and cannot be implemented. +pub trait MisoPin: private::Sealed {} +/// MOSI pin. This trait is sealed and cannot be implemented. +pub trait MosiPin: private::Sealed {} + +macro_rules! pins { + ($spi:ident, $af:literal, SCK: [$($sck:ident),*], MISO: [$($miso:ident),*], MOSI: [$($mosi:ident),*]) => { + $( + impl private::Sealed for $sck> {} + impl SckPin<$spi> for $sck> {} + )* + $( + impl private::Sealed for $miso> {} + impl MisoPin<$spi> for $miso> {} + )* + $( + impl private::Sealed for $mosi> {} + impl MosiPin<$spi> for $mosi> {} + )* + } +} + +/// SPI peripheral operating in full duplex master mode +pub struct Spi { + spi: SPI, + pins: PINS, +} + +macro_rules! hal { + ($($SPIX:ident: ($spiX:ident, $spiX_slave:ident, $pclkX:ident),)+) => { + $( + impl Spi<$SPIX, (SCK, MISO, MOSI)> { + /// Configures the SPI peripheral to operate in full duplex master mode + #[allow(unused_unsafe)] // Necessary for stm32l4r9 + pub fn $spiX( + spi: $SPIX, + pins: (SCK, MISO, MOSI), + mode: Mode, + lsbfirst: bool, + freq: Hertz, + clocks: Clocks, + apb2: &mut <$SPIX as RccBus>::Bus, + ) -> Self + where + SCK: SckPin<$SPIX>, + MISO: MisoPin<$SPIX>, + MOSI: MosiPin<$SPIX>, + { + // enable or reset $SPIX + <$SPIX>::enable(apb2); + <$SPIX>::reset(apb2); + + // FRXTH: RXNE event is generated if the FIFO level is greater than or equal to + // 8-bit + // DS: 8-bit data size + // SSOE: Slave Select output disabled + spi.cr2 + .write(|w| unsafe { + w.frxth().set_bit().ds().bits(0b111).ssoe().clear_bit() + }); + + let br = Self::compute_baud_rate(clocks.$pclkX(), freq); + + // CPHA: phase + // CPOL: polarity + // MSTR: master mode + // BR: 1 MHz + // SPE: SPI disabled + // LSBFIRST: MSB first + // SSM: enable software slave management (NSS pin free for other uses) + // SSI: set nss high = master mode + // CRCEN: hardware CRC calculation disabled + // BIDIMODE: 2 line unidirectional (full duplex) + spi.cr1.write(|w| unsafe { + w.cpha() + .bit(mode.phase == Phase::CaptureOnSecondTransition) + .cpol() + .bit(mode.polarity == Polarity::IdleHigh) + .mstr() + .set_bit() + .br() + .bits(br) + .spe() + .set_bit() + .lsbfirst() + .bit(lsbfirst) + .ssi() + .set_bit() + .ssm() + .set_bit() + .crcen() + .clear_bit() + .bidimode() + .clear_bit() + }); + + Spi { spi, pins } + } + + pub fn $spiX_slave( + spi: $SPIX, + pins: (SCK, MISO, MOSI), + mode: Mode, + lsbfirst: bool, + apb2: &mut <$SPIX as RccBus>::Bus + ) -> Self + where + SCK: SckPin<$SPIX>, + MISO: MisoPin<$SPIX>, + MOSI: MosiPin<$SPIX>, + { + // enable or reset $SPIX + <$SPIX>::enable(apb2); + <$SPIX>::reset(apb2); + + // CPOL: polarity + // CPHA: phase + // BIDIMODE: 2 line unidirectional (full duplex) + // LSBFIRST: MSB first + // CRCEN: hardware CRC calculation disabled + // MSTR: master mode + // SSM: disable software slave management (NSS pin not free for other uses) + // SPE: SPI disabled + spi.cr1.write(|w| { + w.cpol() + .bit(mode.polarity == Polarity::IdleHigh) + .cpha() + .bit(mode.phase == Phase::CaptureOnSecondTransition) + .bidimode() + .clear_bit() + .lsbfirst() + .bit(lsbfirst) + .crcen() + .clear_bit() + .ssm() + .clear_bit() + .mstr() + .clear_bit() + }); + + // DS: 8-bit data size + // FRXTH: RXNE event is generated if the FIFO level is greater than or equal to + // 8-bit + spi.cr2 + .write(|w| unsafe { w.ds().bits(0b111).frxth().set_bit() }); + + // SPE: SPI enabled + spi.cr1.write(|w| w.spe().set_bit()); + + Spi { spi, pins } + } + + pub fn clear_overrun(&mut self) { + self.spi.dr.read().dr(); + self.spi.sr.read().ovr(); + } + + /// Change the baud rate of the SPI + #[allow(unused_unsafe)] // Necessary for stm32l4r9 + pub fn reclock(&mut self, freq: Hertz, clocks: Clocks) { + self.spi.cr1.modify(|_, w| w.spe().clear_bit()); + self.spi.cr1.modify(|_, w| unsafe { + w.br().bits(Self::compute_baud_rate(clocks.$pclkX(), freq)); + w.spe().set_bit() + }); + } + + fn compute_baud_rate(clocks: Hertz, freq: Hertz) -> u8 { + match clocks / freq { + 0 => unreachable!(), + 1..=2 => 0b000, + 3..=5 => 0b001, + 6..=11 => 0b010, + 12..=23 => 0b011, + 24..=39 => 0b100, + 40..=95 => 0b101, + 96..=191 => 0b110, + _ => 0b111, + } + } + + /// Releases the SPI peripheral and associated pins + pub fn free(self) -> ($SPIX, (SCK, MISO, MOSI)) { + (self.spi, self.pins) + } + } + + impl FullDuplex for Spi<$SPIX, PINS> { + type Error = Error; + + fn read(&mut self) -> nb::Result { + let sr = self.spi.sr.read(); + + Err(if sr.ovr().bit_is_set() { + nb::Error::Other(Error::Overrun) + } else if sr.modf().bit_is_set() { + nb::Error::Other(Error::ModeFault) + } else if sr.crcerr().bit_is_set() { + nb::Error::Other(Error::Crc) + } else if sr.rxne().bit_is_set() { + // NOTE(read_volatile) read only 1 byte (the svd2rust API only allows + // reading a half-word) + return Ok(unsafe { + ptr::read_volatile(&self.spi.dr as *const _ as *const u8) + }); + } else { + nb::Error::WouldBlock + }) + } + + fn send(&mut self, byte: u8) -> nb::Result<(), Error> { + let sr = self.spi.sr.read(); + + Err(if sr.ovr().bit_is_set() { + nb::Error::Other(Error::Overrun) + } else if sr.modf().bit_is_set() { + nb::Error::Other(Error::ModeFault) + } else if sr.crcerr().bit_is_set() { + nb::Error::Other(Error::Crc) + } else if sr.txe().bit_is_set() { + // NOTE(write_volatile) see note above + unsafe { ptr::write_volatile(&self.spi.dr as *const _ as *mut u8, byte) } + return Ok(()); + } else { + nb::Error::WouldBlock + }) + } + } + + impl crate::hal::blocking::spi::transfer::Default for Spi<$SPIX, PINS> {} + + impl crate::hal::blocking::spi::write::Default for Spi<$SPIX, PINS> {} + )+ + } +} + +use crate::gpio::gpiod::*; +#[cfg(any( + // feature = "stm32l471", // missing PAC support for Port G + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +use crate::gpio::gpiog::*; +use crate::gpio::{gpioa::*, gpiob::*, gpioc::*, gpioe::*}; + +use crate::stm32::SPI1; +hal! { + SPI1: (spi1, spi1_slave, pclk2), +} + +pins!(SPI1, 5, + SCK: [PA5, PB3, PE13], + MISO: [PA6, PB4, PE14], + MOSI: [PA7, PB5, PE15]); + +#[cfg(any( + // feature = "stm32l471", // missing PAC support for Port G + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +pins!(SPI1, 5, SCK: [PG2], MISO: [PG3], MOSI: [PG4]); + +#[cfg(not(any(feature = "stm32l433", feature = "stm32l443",)))] +use crate::stm32::SPI3; + +#[cfg(not(any(feature = "stm32l433", feature = "stm32l443",)))] +hal! { + SPI3: (spi3, spi3_slave, pclk1), +} + +#[cfg(not(any(feature = "stm32l433", feature = "stm32l443",)))] +pins!(SPI3, 6, + SCK: [PB3, PC10], + MISO: [PB4, PC11], + MOSI: [PB5, PC12]); + +#[cfg(any( + // feature = "stm32l471", // missing PAC support for Port G + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +pins!(SPI3, 6, SCK: [PG9], MISO: [PG10], MOSI: [PG11]); + +use crate::stm32::SPI2; + +hal! { + SPI2: (spi2, spi2_slave, pclk1), +} + +pins!(SPI2, 5, + SCK: [PB13, PB10, PD1], + MISO: [PB14, PC2, PD3], + MOSI: [PB15, PC3, PD4]); + +pub struct SpiPayload { + spi: Spi, +} + +pub type SpiRxDma = dma::RxDma, CHANNEL>; + +pub type SpiTxDma = dma::TxDma, CHANNEL>; + +pub type SpiRxTxDma = dma::RxTxDma, RXCH, TXCH>; + +macro_rules! spi_dma { + ($SPIX:ident, $RX_CH:path, $RX_CHSEL:path, $TX_CH:path, $TX_CHSEL:path) => { + impl dma::Receive for SpiRxDma<$SPIX, PINS, $RX_CH> { + type RxChannel = $RX_CH; + type TransmittedWord = u8; + } + + impl dma::Transmit for SpiTxDma<$SPIX, PINS, $TX_CH> { + type TxChannel = $TX_CH; + type ReceivedWord = u8; + } + + impl dma::ReceiveTransmit for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + type RxChannel = $RX_CH; + type TxChannel = $TX_CH; + type TransferedWord = u8; + } + + impl Spi<$SPIX, PINS> { + pub fn with_rx_dma(self, mut channel: $RX_CH) -> SpiRxDma<$SPIX, PINS, $RX_CH> { + let payload = SpiPayload { spi: self }; + + // Perform one-time setup actions to keep the work minimal when using the driver. + + channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + channel.set_request_line($RX_CHSEL).unwrap(); + channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + + SpiRxDma { payload, channel } + } + + pub fn with_tx_dma(self, mut channel: $TX_CH) -> SpiTxDma<$SPIX, PINS, $TX_CH> { + let payload = SpiPayload { spi: self }; + + // Perform one-time setup actions to keep the work minimal when using the driver. + + channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + channel.set_request_line($TX_CHSEL).unwrap(); + channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + + SpiTxDma { payload, channel } + } + + pub fn with_rxtx_dma( + self, + mut rx_channel: $RX_CH, + mut tx_channel: $TX_CH, + ) -> SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + let payload = SpiPayload { spi: self }; + + // Perform one-time setup actions to keep the work minimal when using the driver. + + // + // Setup RX channel + // + rx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + rx_channel.set_request_line($RX_CHSEL).unwrap(); + + rx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + + // + // Setup TX channel + // + tx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + tx_channel.set_request_line($TX_CHSEL).unwrap(); + + tx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + + SpiRxTxDma { + payload, + rx_channel, + tx_channel, + } + } + } + + impl SpiRxDma<$SPIX, PINS, $RX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $RX_CH) { + self.stop(); + (self.payload.spi, self.channel) + } + } + + impl SpiTxDma<$SPIX, PINS, $TX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $TX_CH) { + self.stop(); + (self.payload.spi, self.channel) + } + } + + impl SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $RX_CH, $TX_CH) { + self.stop(); + (self.payload.spi, self.rx_channel, self.tx_channel) + } + } + + impl dma::TransferPayload for SpiRxDma<$SPIX, PINS, $RX_CH> { + fn start(&mut self) { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 0. SPI disabled during setup. + // 1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used. + // 2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used. + // 3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used. + // 4. Enable the SPI by setting the SPE bit. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 0. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().set_bit()); // 1. + self.channel.start(); // 2. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().set_bit()); // 4. + } + + fn stop(&mut self) { + // Stop DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. + // 2. Disable the SPI by following the SPI disable procedure. + // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the + // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. + self.channel.stop(); // 1. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 2. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().clear_bit()); // 3. + } + } + + impl dma::TransferPayload for SpiTxDma<$SPIX, PINS, $TX_CH> { + fn start(&mut self) { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 0. SPI disabled during setup. + // 1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used. + // 2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used. + // 3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used. + // 4. Enable the SPI by setting the SPE bit. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 0. + self.channel.start(); // 2. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().set_bit()); // 3. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().set_bit()); // 4. + } + + fn stop(&mut self) { + // Stop DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. + // 2. Disable the SPI by following the SPI disable procedure. + // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the + // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. + self.channel.stop(); // 1. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 2. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().clear_bit()); // 3. + } + } + + impl dma::TransferPayload for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + fn start(&mut self) { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 0. SPI disabled during setup. + // 1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used. + // 2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used. + // 3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used. + // 4. Enable the SPI by setting the SPE bit. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 0. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().set_bit()); // 1. + self.rx_channel.start(); // 2. + self.tx_channel.start(); // 2. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().set_bit()); // 3. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().set_bit()); // 4. + } + + fn stop(&mut self) { + // Stop DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. + // 2. Disable the SPI by following the SPI disable procedure. + // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the + // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. + self.tx_channel.stop(); // 1. + self.rx_channel.stop(); // 1. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 2. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().clear_bit().txdmaen().clear_bit()); // 3. + } + } + + impl dma::ReadDma for SpiRxDma<$SPIX, PINS, $RX_CH> + where + B: StaticWriteBuffer, + { + fn read(mut self, mut buffer: B) -> dma::Transfer { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)" + + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + + // Setup RX channel addresses and length + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); + + // Fences and start + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::w(buffer, self) + } + } + + impl dma::WriteDma for SpiTxDma<$SPIX, PINS, $TX_CH> + where + B: StaticReadBuffer, + { + fn write(mut self, buffer: B) -> dma::Transfer { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)" + + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_read_buffer() }; + + // Setup TX channel addresses and length + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); + + // Fences and start + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::r(buffer, self) + } + } + + impl dma::TransferDma for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> + where + B: StaticWriteBuffer, + { + fn transfer(mut self, mut buffer: B) -> dma::Transfer { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)" + + // Transfer: we use the same buffer for RX and TX + + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + + // Setup RX channel addresses and length + self.rx_channel.set_memory_address(ptr as u32, true); + self.rx_channel.set_transfer_length(len as u16); + + // Setup TX channel addresses and length + self.tx_channel.set_memory_address(ptr as u32, true); + self.tx_channel.set_transfer_length(len as u16); + + // Fences and start + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::rw(buffer, self) + } + } + }; +} + +spi_dma!(SPI1, dma1::C2, DmaInput::Spi1Rx, dma1::C3, DmaInput::Spi1Tx); +#[cfg(not(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", +)))] +spi_dma!(SPI2, dma1::C4, DmaInput::Spi2Rx, dma1::C5, DmaInput::Spi2Tx); +// spi_dma!(SPI1, dma2::C3, c3s, map4, dma2::C4, c4s, map4); +#[cfg(not(any(feature = "stm32l433", feature = "stm32l443",)))] +spi_dma!(SPI3, dma2::C1, DmaInput::Spi3Rx, dma2::C2, DmaInput::Spi3Tx); diff --git a/stm32l4xx-hal/src/time.rs b/stm32l4xx-hal/src/time.rs new file mode 100644 index 0000000..e2d2678 --- /dev/null +++ b/stm32l4xx-hal/src/time.rs @@ -0,0 +1,70 @@ +//! Time units + +pub use fugit::{ + HertzU32 as Hertz, KilohertzU32 as KiloHertz, MegahertzU32 as MegaHertz, + MicrosDurationU32 as MicroSeconds, MillisDurationU32 as MilliSeconds, +}; + +use crate::rcc::Clocks; +use cortex_m::peripheral::DWT; + +/// Bits per second +#[derive(Clone, Copy, Debug)] +pub struct Bps(pub u32); + +/// Extension trait that adds convenience methods to the `u32` type +pub trait U32Ext { + /// Wrap in `Bps` + fn bps(self) -> Bps; +} + +impl U32Ext for u32 { + fn bps(self) -> Bps { + Bps(self) + } +} + +/// A monotonic nondecreasing timer +#[derive(Clone, Copy, Debug)] +pub struct MonoTimer { + frequency: Hertz, +} + +impl MonoTimer { + /// Creates a new `Monotonic` timer + pub fn new(mut dwt: DWT, clocks: Clocks) -> Self { + dwt.enable_cycle_counter(); + + // now the CYCCNT counter can't be stopped or resetted + drop(dwt); + + MonoTimer { + frequency: clocks.sysclk(), + } + } + + /// Returns the frequency at which the monotonic timer is operating at + pub fn frequency(&self) -> Hertz { + self.frequency + } + + /// Returns an `Instant` corresponding to "now" + pub fn now(&self) -> Instant { + Instant { + now: DWT::cycle_count(), + } + } +} + +/// A measurement of a monotonically nondecreasing clock +#[derive(Clone, Copy, Debug)] +pub struct Instant { + now: u32, +} + +impl Instant { + /// Ticks elapsed since the `Instant` was created + pub fn elapsed(&self) -> u32 { + DWT::cycle_count().wrapping_sub(self.now) + } +} diff --git a/stm32l4xx-hal/src/timer.rs b/stm32l4xx-hal/src/timer.rs new file mode 100644 index 0000000..739966f --- /dev/null +++ b/stm32l4xx-hal/src/timer.rs @@ -0,0 +1,327 @@ +//! Timers + +use crate::hal::timer::{CountDown, Periodic}; +// missing PAC support +/* +#[cfg(any( + feature = "stm32l451", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l471", + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +use crate::stm32::TIM3; +*/ +#[cfg(not(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l451", + feature = "stm32l452", + feature = "stm32l462", +)))] +use crate::stm32::TIM7; +use crate::stm32::{TIM15, TIM16, TIM2, TIM6}; +#[cfg(any( + // feature = "stm32l471", // missing PAC support + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +use crate::stm32::{TIM17, TIM4, TIM5}; + +// TIM1/TIM8 ("Advcanced Control Timers") -> no impl +// TIM2/TIM3/TIM4/TIM5 ("General Purpose Timers") +// TIM15/TIM16/TIM17 ("General Purpose Timers") +// TIM6/TIM7 ("Basic Timers") +// LPTIM ("Low power Timer") -> no impl + +use cast::{u16, u32}; +use void::Void; + +use crate::rcc::{Clocks, Enable, Reset, APB1R1, APB2}; +use crate::time::Hertz; +use fugit::RateExtU32; + +/// Hardware timers +pub struct Timer { + clocks: Clocks, + tim: TIM, + timeout: Hertz, +} + +/// Interrupt events +pub enum Event { + /// Timer timed out / count down ended + TimeOut, +} + +macro_rules! hal { + ($($TIM:ident: ($tim:ident, $frname:ident, $apb:ident, $width:ident),)+) => { + $( + impl Periodic for Timer<$TIM> {} + + impl CountDown for Timer<$TIM> { + type Time = Hertz; + + // NOTE(allow) `w.psc().bits()` is safe for TIM{6,7} but not for TIM{2,3,4} due to + // some SVD omission + #[allow(unused_unsafe)] + fn start(&mut self, timeout: T) + where + T: Into, + { + // pause + self.tim.cr1.modify(|_, w| w.cen().clear_bit()); + + self.timeout = timeout.into(); + let ticks = self.clocks.pclk1() / self.timeout; // TODO check pclk that timer is on + let psc = u16((ticks - 1) / (1 << 16)).unwrap(); + + self.tim.psc.write(|w| unsafe { w.psc().bits(psc) }); + + let arr = u16(ticks / u32(psc + 1)).unwrap(); + + self.tim.arr.write(|w| unsafe { w.bits(u32(arr)) }); + + // Trigger an update event to load the prescaler value to the clock + self.tim.egr.write(|w| w.ug().set_bit()); + // The above line raises an update event which will indicate + // that the timer is already finished. Since this is not the case, + // it should be cleared + self.clear_update_interrupt_flag(); + + // start counter + self.tim.cr1.modify(|_, w| w.cen().set_bit()); + } + + fn wait(&mut self) -> nb::Result<(), Void> { + if self.tim.sr.read().uif().bit_is_clear() { + Err(nb::Error::WouldBlock) + } else { + self.clear_update_interrupt_flag(); + Ok(()) + } + } + } + + impl Timer<$TIM> { + // XXX(why not name this `new`?) bummer: constructors need to have different names + // even if the `$TIM` are non overlapping (compare to the `free` function below + // which just works) + /// Configures a TIM peripheral as a periodic count down timer + pub fn $tim(tim: $TIM, timeout: Hertz, clocks: Clocks, apb: &mut $apb) -> Self { + // enable and reset peripheral to a clean slate state + <$TIM>::enable(apb); + <$TIM>::reset(apb); + + let mut timer = Timer { + clocks, + tim, + timeout: 0.Hz(), + }; + timer.start(timeout); + + timer + } + + /// Start a free running, monotonic, timer running at some specific frequency. + /// + /// May generate events on overflow of the timer. + pub fn $frname( + tim: $TIM, + clocks: Clocks, + frequency: Hertz, + event_on_overflow: bool, + apb: &mut $apb, + ) -> Self { + <$TIM>::enable(apb); + <$TIM>::reset(apb); + + let psc = clocks.pclk1() / frequency - 1; + + debug_assert!(clocks.pclk1() >= frequency); + debug_assert!(frequency.raw() > 0); + debug_assert!(psc <= core::u16::MAX.into()); + + tim.psc.write(|w| w.psc().bits((psc as u16).into()) ); + let max = core::$width::MAX; + tim.arr.write(|w| unsafe { w.bits(max.into()) }); + + // Trigger an update event to load the prescaler value to the clock + tim.egr.write(|w| w.ug().set_bit()); + + + // The above line raises an update event which will indicate + // that the timer is already finished. Since this is not the case, + // it should be cleared + tim.sr.modify(|_, w| w.uif().clear_bit()); + + // start counter + tim.cr1.modify(|_, w| { + w.cen().set_bit(); + + if event_on_overflow { + w.udis().clear_bit(); + } else { + w.udis().set_bit(); + } + + w + }); + + Timer { + clocks, + tim, + timeout: frequency, + } + } + + /// Starts listening for an `event` + pub fn listen(&mut self, event: Event) { + match event { + Event::TimeOut => { + // Enable update event interrupt + self.tim.dier.write(|w| w.uie().set_bit()); + } + } + } + + + /// Clears interrupt associated with `event`. + /// + /// If the interrupt is not cleared, it will immediately retrigger after + /// the ISR has finished. + pub fn clear_interrupt(&mut self, event: Event) { + match event { + Event::TimeOut => { + // Clear interrupt flag + self.tim.sr.write(|w| w.uif().clear_bit()); + } + } + } + + + /// Stops listening for an `event` + pub fn unlisten(&mut self, event: Event) { + match event { + Event::TimeOut => { + // Enable update event interrupt + self.tim.dier.write(|w| w.uie().clear_bit()); + } + } + } + + /// Clears Update Interrupt Flag + pub fn clear_update_interrupt_flag(&mut self) { + self.tim.sr.modify(|_, w| w.uif().clear_bit()); + } + + /// Get the count of the timer. + pub fn count() -> $width { + let cnt = unsafe { (*$TIM::ptr()).cnt.read() }; + cnt.cnt().bits() + } + + /// Releases the TIM peripheral + pub fn free(self) -> $TIM { + // pause counter + self.tim.cr1.modify(|_, w| w.cen().clear_bit()); + self.tim + } + } + )+ + } +} + +hal! { + TIM2: (tim2, free_running_tim2, APB1R1, u32), + TIM6: (tim6, free_running_tim6, APB1R1, u16), + //TIM7: (tim7, free_running_tim7, APB1R1, u16), + TIM15: (tim15, free_running_tim15, APB2, u16), + TIM16: (tim16, free_running_tim16, APB2, u16), +} + +// missing PAC support +// RCC_APB1RSTR1->TIM3RST not defined +/* +#[cfg(any( + feature = "stm32l451", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l471", + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + feature = "stm32l4r9", + feature = "stm32l4s9", +))] +hal! { + TIM3: (tim3, free_running_tim3, tim3en, tim3rst, APB1R1, u32), +} +*/ + +#[cfg(not(any( + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l451", + feature = "stm32l452", + feature = "stm32l462", +)))] +hal! { + TIM7: (tim7, free_running_tim7, APB1R1, u16), +} + +#[cfg(any( + feature = "stm32l475", + feature = "stm32l476", + feature = "stm32l485", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6", + // feature = "stm32l4p5", + // feature = "stm32l4q5", + // feature = "stm32l4r5", + // feature = "stm32l4s5", + // feature = "stm32l4r7", + // feature = "stm32l4s7", + // feature = "stm32l4r9", + // feature = "stm32l4s9", +))] +hal! { + TIM4: (tim4, free_running_tim4, APB1R1, u16), + TIM5: (tim5, free_running_tim5, APB1R1, u32), + TIM17: (tim17, free_running_tim17, APB2, u16), +} diff --git a/stm32l4xx-hal/src/traits/flash.rs b/stm32l4xx-hal/src/traits/flash.rs new file mode 100644 index 0000000..e862469 --- /dev/null +++ b/stm32l4xx-hal/src/traits/flash.rs @@ -0,0 +1,55 @@ +/// Flash page representation where each flash page represents a region of 2048 bytes. The flash +/// controller can only erase on a page basis. +#[derive(Copy, Clone, Debug)] +pub struct FlashPage(pub usize); + +/// Flash operation error +#[derive(Copy, Clone, Debug)] +pub enum Error { + /// Flash controller is not done yet + Busy, + /// Error detected (by command execution, or because no command could be executed) + Illegal, + /// Set during read if ECC decoding logic detects correctable or uncorrectable error + EccError, + /// Page number is out of range + PageOutOfRange, + /// (Legal) command failed + Failure, +} + +/// A type alias for the result of a Flash operation. +pub type Result = core::result::Result<(), Error>; + +pub trait Read { + /// Native type of the flash for reading with the correct alignment of the memory and size + /// + /// Can be `u8`, `u16`, `u32`, ..., or any user defined type + type NativeType; + + /// Read from the flash memory using the native interface + fn read_native(&self, address: usize, array: &mut [Self::NativeType]); + + /// Read a buffer of bytes from memory + fn read(&self, address: usize, buf: &mut [u8]); +} + +pub trait WriteErase { + /// Native type of the flash for writing with the correct alignment and size + /// + /// Can be `u8`, `u16`, `u32`, ..., or any user defined type + type NativeType; + + /// check flash status + fn status(&self) -> Result; + + /// Erase specified flash page. + fn erase_page(&mut self, page: FlashPage) -> Result; + + /// The smallest possible write, depends on platform + fn write_native(&mut self, address: usize, array: &[Self::NativeType]) -> Result; + + /// Read a buffer of bytes to memory, this uses the native writes internally and if it's not + /// the same length and a set of native writes the write will be padded to fill a native write. + fn write(&mut self, address: usize, data: &[u8]) -> Result; +} diff --git a/stm32l4xx-hal/src/traits/mod.rs b/stm32l4xx-hal/src/traits/mod.rs new file mode 100644 index 0000000..2e50f82 --- /dev/null +++ b/stm32l4xx-hal/src/traits/mod.rs @@ -0,0 +1 @@ +pub mod flash; diff --git a/stm32l4xx-hal/src/tsc.rs b/stm32l4xx-hal/src/tsc.rs new file mode 100644 index 0000000..4cef108 --- /dev/null +++ b/stm32l4xx-hal/src/tsc.rs @@ -0,0 +1,306 @@ +//! Touch sense controller +//! +//! From STM32 (https://www.st.com/content/ccc/resource/technical/document/application_note/9d/be/03/8c/5d/8c/49/50/DM00088471.pdf/files/DM00088471.pdf/jcr:content/translations/en.DM00088471.pdf): +//! +//! The Cs capacitance is a key parameter for sensitivity. For touchkey sensors, the Cs value is +//! usually comprised between 8.7nF to 22nF. For linear and rotary touch sensors, the value is +//! usually comprised between 47nF and 100nF. These values are given as reference for an +//! electrode fitting a human finger tip size across a few millimeters dielectric panel. + +use crate::gpio::gpiob::{PB4, PB5, PB6, PB7}; +use crate::gpio::{Alternate, OpenDrain, PushPull}; +use crate::rcc::{Enable, Reset, AHB1}; +use crate::stm32::TSC; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Event { + /// Max count error + MaxCountError, + /// End of acquisition + EndOfAcquisition, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Error { + /// Max count error + MaxCountError, + /// Wrong GPIO for reading - returns the ioccr register + InvalidPin(u32), +} + +pub trait SamplePin { + const GROUP: u32; + const OFFSET: u32; +} +impl SamplePin for PB4> { + const GROUP: u32 = 2; + const OFFSET: u32 = 0; +} +impl SamplePin for PB5> { + const GROUP: u32 = 2; + const OFFSET: u32 = 1; +} +impl SamplePin for PB6> { + const GROUP: u32 = 2; + const OFFSET: u32 = 2; +} +impl SamplePin for PB7> { + const GROUP: u32 = 2; + const OFFSET: u32 = 3; +} + +pub trait ChannelPin { + const GROUP: u32; + const OFFSET: u32; +} +impl ChannelPin for PB4> { + const GROUP: u32 = 2; + const OFFSET: u32 = 0; +} +impl ChannelPin for PB5> { + const GROUP: u32 = 2; + const OFFSET: u32 = 1; +} +impl ChannelPin for PB6> { + const GROUP: u32 = 2; + const OFFSET: u32 = 2; +} +impl ChannelPin for PB7> { + const GROUP: u32 = 2; + const OFFSET: u32 = 3; +} + +pub struct Tsc { + sample_pin: SPIN, + tsc: TSC, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Config { + pub clock_prescale: Option, + pub max_count_error: Option, + pub charge_transfer_high: Option, + pub charge_transfer_low: Option, + /// Spread spectrum deviation - a value between 0 and 128 + pub spread_spectrum_deviation: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ClockPrescaler { + Hclk = 0b000, + HclkDiv2 = 0b001, + HclkDiv4 = 0b010, + HclkDiv8 = 0b011, + HclkDiv16 = 0b100, + HclkDiv32 = 0b101, + HclkDiv64 = 0b110, + HclkDiv128 = 0b111, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MaxCountError { + /// 000: 255 + U255 = 0b000, + /// 001: 511 + U511 = 0b001, + /// 010: 1023 + U1023 = 0b010, + /// 011: 2047 + U2047 = 0b011, + /// 100: 4095 + U4095 = 0b100, + /// 101: 8191 + U8191 = 0b101, + /// 110: 16383 + U16383 = 0b110, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +/// How many tsc cycles are spent charging / discharging +pub enum ChargeDischargeTime { + C1 = 0b0000, + C2 = 0b0001, + C3 = 0b0010, + C4 = 0b0011, + C5 = 0b0100, + C6 = 0b0101, + C7 = 0b0110, + C8 = 0b0111, + C9 = 0b1000, + C10 = 0b1001, + C11 = 0b1010, + C12 = 0b1011, + C13 = 0b1100, + C14 = 0b1101, + C15 = 0b1110, + C16 = 0b1111, +} + +impl Tsc { + pub fn tsc(tsc: TSC, sample_pin: SPIN, ahb: &mut AHB1, cfg: Option) -> Self + where + SPIN: SamplePin, + { + /* Enable the peripheral clock */ + TSC::enable(ahb); + TSC::reset(ahb); + + let config = cfg.unwrap_or(Config { + clock_prescale: None, + max_count_error: None, + charge_transfer_high: None, + charge_transfer_low: None, + spread_spectrum_deviation: None, + }); + + tsc.cr.write(|w| unsafe { + w.ctph() + .bits( + config + .charge_transfer_high + .unwrap_or(ChargeDischargeTime::C2) as u8, + ) + .ctpl() + .bits( + config + .charge_transfer_low + .unwrap_or(ChargeDischargeTime::C2) as u8, + ) + .pgpsc() + .bits(config.clock_prescale.unwrap_or(ClockPrescaler::Hclk) as u8) + .mcv() + .bits(config.max_count_error.unwrap_or(MaxCountError::U8191) as u8) + .sse() + .bit(config.spread_spectrum_deviation.is_some()) + .ssd() + .bits(config.spread_spectrum_deviation.unwrap_or(0u8)) + .tsce() + .set_bit() + }); + + let bit_pos = SPIN::OFFSET + (4 * (SPIN::GROUP - 1)); + + // Schmitt trigger hysteresis on sample IOs + tsc.iohcr.write(|w| unsafe { w.bits(1 << bit_pos) }); + + // Set the sampling pin + tsc.ioscr.write(|w| unsafe { w.bits(1 << bit_pos) }); + + // set the acquisitiuon groups based of the channel pins, stm32l432xx only has group 2 + tsc.iogcsr.write(|w| w.g2e().set_bit()); + + // clear interrupt & flags + tsc.icr.write(|w| w.eoaic().set_bit().mceic().set_bit()); + + Tsc { tsc, sample_pin } + } + + /// Starts a charge acquisition + pub fn start(&self, _input: &mut PIN) + where + PIN: ChannelPin, + { + self.clear(Event::EndOfAcquisition); + self.clear(Event::MaxCountError); + + // discharge the caps ready for a new reading + self.tsc.cr.modify(|_, w| w.iodef().clear_bit()); + + let bit_pos = PIN::OFFSET + (4 * (PIN::GROUP - 1)); + + // Set the channel pin + self.tsc.ioccr.write(|w| unsafe { w.bits(1 << bit_pos) }); + + self.tsc.cr.modify(|_, w| w.start().set_bit()); + } + + /// Clear interrupt & flags + pub fn clear(&self, event: Event) { + match event { + Event::EndOfAcquisition => { + self.tsc.icr.write(|w| w.eoaic().set_bit()); + } + Event::MaxCountError => { + self.tsc.icr.write(|w| w.mceic().set_bit()); + } + } + } + + /// Blocks waiting for a acquisition to complete or for a Max Count Error + pub fn acquire(&self, input: &mut PIN) -> Result + where + PIN: ChannelPin, + { + // start the acq + self.start(input); + + let result = loop { + let isr = self.tsc.isr.read(); + if isr.eoaf().bit_is_set() { + self.tsc.icr.write(|w| w.eoaic().set_bit()); + break Ok(self.read_unchecked()); + } else if isr.mcef().bit_is_set() { + self.tsc.icr.write(|w| w.mceic().set_bit()); + break Err(Error::MaxCountError); + } + }; + self.tsc.ioccr.write(|w| unsafe { w.bits(0b0) }); // clear channel register + result + } + + /// Reads the tsc group 2 count register + pub fn read(&self, _input: &mut PIN) -> Result + where + PIN: ChannelPin, + { + let bit_pos = PIN::OFFSET + (4 * (PIN::GROUP - 1)); + // Read the current channel config + let channel = self.tsc.ioccr.read().bits(); + // if they are equal we have the right pin + if channel == (1 << bit_pos) { + Ok(self.read_unchecked()) + } else { + Err(Error::InvalidPin(channel)) + } + } + + /// Reads the tsc group 2 count register + /// WARNING, just returns the contents of the register! No validation of the correct pin + pub fn read_unchecked(&self) -> u16 { + self.tsc.iog2cr.read().cnt().bits() + } + + /// Is the tsc performing an aquisition + pub fn in_progress(&mut self) -> bool { + self.tsc.cr.read().start().bit_is_set() + } + + /// Enables an interrupt event + pub fn listen(&mut self, event: Event) { + match event { + Event::EndOfAcquisition => { + self.tsc.ier.modify(|_, w| w.eoaie().set_bit()); + } + Event::MaxCountError => { + self.tsc.ier.modify(|_, w| w.mceie().set_bit()); + } + } + } + + /// Disables an interrupt event + pub fn unlisten(&self, event: Event) { + match event { + Event::EndOfAcquisition => { + self.tsc.ier.modify(|_, w| w.eoaie().clear_bit()); + } + Event::MaxCountError => { + self.tsc.ier.modify(|_, w| w.mceie().clear_bit()); + } + } + } + + /// Releases the TSC peripheral and associated pins + pub fn free(self) -> (TSC, SPIN) { + (self.tsc, self.sample_pin) + } +} diff --git a/stm32l4xx-hal/src/usb.rs b/stm32l4xx-hal/src/usb.rs new file mode 100644 index 0000000..c6b39c7 --- /dev/null +++ b/stm32l4xx-hal/src/usb.rs @@ -0,0 +1,48 @@ +//! USB peripheral +//! +//! Requires the `stm32-usbd` feature. +//! +//! See +//! for usage examples. + +use crate::rcc::{Enable, Reset}; +use crate::stm32::USB; +use stm32_usbd::UsbPeripheral; + +use crate::gpio::gpioa::{PA11, PA12}; +use crate::gpio::{Alternate, PushPull}; +pub use stm32_usbd::UsbBus; + +pub struct Peripheral { + pub usb: USB, + pub pin_dm: PA11>, + pub pin_dp: PA12>, +} + +unsafe impl Sync for Peripheral {} + +unsafe impl UsbPeripheral for Peripheral { + const REGISTERS: *const () = USB::ptr() as *const (); + const DP_PULL_UP_FEATURE: bool = true; + const EP_MEMORY: *const () = 0x4000_6c00 as _; + const EP_MEMORY_SIZE: usize = 1024; + const EP_MEMORY_ACCESS_2X16: bool = true; + + fn enable() { + cortex_m::interrupt::free(|_| unsafe { + // Enable USB peripheral + USB::enable_unchecked(); + + // Reset USB peripheral + USB::reset_unchecked(); + }); + } + + fn startup_delay() { + // There is a chip specific startup delay. For STM32F103xx it's 1µs and this should wait for + // at least that long. + cortex_m::asm::delay(72); + } +} + +pub type UsbBusType = UsbBus; diff --git a/stm32l4xx-hal/src/watchdog.rs b/stm32l4xx-hal/src/watchdog.rs new file mode 100644 index 0000000..de60d9c --- /dev/null +++ b/stm32l4xx-hal/src/watchdog.rs @@ -0,0 +1,140 @@ +//! Watchdog peripherals + +use crate::{ + hal::watchdog::{Watchdog, WatchdogEnable}, + stm32::{DBGMCU, IWDG}, + time::MilliSeconds, +}; + +/// Wraps the Independent Watchdog (IWDG) peripheral +pub struct IndependentWatchdog { + iwdg: IWDG, +} + +const LSI_KHZ: u32 = 32; +const MAX_PR: u32 = 0b110; +const MAX_RL: u16 = 0xFFF; +const KR_ACCESS: u16 = 0x5555; +const KR_RELOAD: u16 = 0xAAAA; +const KR_START: u16 = 0xCCCC; + +impl IndependentWatchdog { + /// Creates a new `IndependentWatchDog` without starting it. Call `start` to start the watchdog. + /// See `WatchdogEnable` and `Watchdog` for more info. + pub fn new(iwdg: IWDG) -> Self { + IndependentWatchdog { iwdg } + } + + /// Debug independent watchdog stopped when core is halted + pub fn stop_on_debug(&self, dbgmcu: &DBGMCU, stop: bool) { + #[cfg(any( + feature = "stm32l431", + feature = "stm32l451", + feature = "stm32l471", + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443", + ))] + dbgmcu.apb1fzr1.modify(|_, w| w.dbg_iwdg_stop().bit(stop)); + #[cfg(not(any( + feature = "stm32l431", + feature = "stm32l451", + feature = "stm32l471", + feature = "stm32l412", + feature = "stm32l422", + feature = "stm32l432", + feature = "stm32l442", + feature = "stm32l452", + feature = "stm32l462", + feature = "stm32l433", + feature = "stm32l443", + )))] + dbgmcu.apb1_fzr1.modify(|_, w| w.dbg_iwdg_stop().bit(stop)); + } + + /// Sets the watchdog timer timout period. Max: 32768 ms + fn setup(&self, timeout_ms: MilliSeconds) { + assert!(timeout_ms.ticks() < (1 << 15), "Watchdog timeout to high"); + let pr = match timeout_ms.ticks() { + t if t == 0 => 0b000, // <= (MAX_PR + 1) * 4 / LSI_KHZ => 0b000, + t if t <= (MAX_PR + 1) * 8 / LSI_KHZ => 0b001, + t if t <= (MAX_PR + 1) * 16 / LSI_KHZ => 0b010, + t if t <= (MAX_PR + 1) * 32 / LSI_KHZ => 0b011, + t if t <= (MAX_PR + 1) * 64 / LSI_KHZ => 0b100, + t if t <= (MAX_PR + 1) * 128 / LSI_KHZ => 0b101, + _ => 0b110, + }; + + let max_period = Self::timeout_period(pr, MAX_RL); + let max_rl = u32::from(MAX_RL); + let rl = (timeout_ms.ticks() * max_rl / max_period).min(max_rl) as u16; + + self.access_registers(|iwdg| { + iwdg.pr.modify(|_, w| w.pr().bits(pr)); + iwdg.rlr.modify(|_, w| w.rl().bits(rl)); + }); + } + + fn is_pr_updating(&self) -> bool { + self.iwdg.sr.read().pvu().bit() + } + + /// Returns the interval in ms + pub fn interval(&self) -> MilliSeconds { + while self.is_pr_updating() {} + + let pr = self.iwdg.pr.read().pr().bits(); + let rl = self.iwdg.rlr.read().rl().bits(); + let ms = Self::timeout_period(pr, rl); + MilliSeconds::from_ticks(ms) + } + + /// pr: Prescaler divider bits, rl: reload value + /// + /// Returns timeout period in ms + fn timeout_period(pr: u8, rl: u16) -> u32 { + let divider: u32 = match pr { + 0b000 => 4, + 0b001 => 8, + 0b010 => 16, + 0b011 => 32, + 0b100 => 64, + 0b101 => 128, + 0b110 => 256, + 0b111 => 256, + _ => unreachable!(), + }; + (u32::from(rl) + 1) * divider / LSI_KHZ + } + + fn access_registers A>(&self, mut f: F) -> A { + // Unprotect write access to registers + self.iwdg.kr.write(|w| unsafe { w.key().bits(KR_ACCESS) }); + let a = f(&self.iwdg); + + // Protect again + self.iwdg.kr.write(|w| unsafe { w.key().bits(KR_RELOAD) }); + a + } +} + +impl WatchdogEnable for IndependentWatchdog { + type Time = MilliSeconds; + + fn start>(&mut self, period: T) { + self.setup(period.into()); + + self.iwdg.kr.write(|w| unsafe { w.key().bits(KR_START) }); + } +} + +impl Watchdog for IndependentWatchdog { + fn feed(&mut self) { + self.iwdg.kr.write(|w| unsafe { w.key().bits(KR_RELOAD) }); + } +} diff --git a/stm32l4xx-hal/tools/check.py b/stm32l4xx-hal/tools/check.py new file mode 100644 index 0000000..3384ecd --- /dev/null +++ b/stm32l4xx-hal/tools/check.py @@ -0,0 +1,47 @@ +#! /usr/bin/env python3 + +import json +import subprocess +import sys + + +def run_inner(args): + print("Running `{}`...".format(" ".join(args))) + ret = subprocess.call(args) == 0 + print("") + return ret + + +def run(mcu, cargo_cmd): + if mcu == "": + return run_inner(cargo_cmd) + else: + return run_inner(cargo_cmd + ["--features={}".format(mcu)]) + + +def main(): + cargo_meta = json.loads( + subprocess.check_output("cargo metadata --no-deps --format-version=1", + shell=True, + universal_newlines=True) + ) + + crate_info = cargo_meta["packages"][0] + + features = ["{},rt".format(x) + for x in crate_info["features"].keys() + if x != "rt" + if x != "unproven"] + + if 'size_check' in sys.argv: + cargo_cmd = ['cargo', 'build', '--release'] + else: + cargo_cmd = ['cargo', 'check'] + + if not all(map(lambda f: run(f, cargo_cmd), + features)): + sys.exit(-1) + + +if __name__ == "__main__": + main() \ No newline at end of file