├── examples ├── usb_otg_no_crs_rtic │ ├── .envrc │ ├── rust-toolchain.toml │ ├── .cargo │ │ └── config.toml │ ├── shell.nix │ ├── memory.x │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── blinky │ ├── memory.x │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── sawtooth │ ├── memory.x │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── conductivity_module │ ├── memory.x │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── syntax_overview │ ├── memory.x │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── blinky_timer_interrupt │ ├── memory.x │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── comp.rs ├── rtc.rs ├── flash.rs ├── can-send.rs ├── uart.rs ├── timer.rs ├── i2c.rs ├── spi.rs ├── can.rs ├── usb_serial.rs ├── interrupts.rs ├── waveform_generator.rs ├── adc.rs ├── clock_cfg.rs └── gpio.rs ├── .gitignore ├── rustfmt.toml ├── screenshots └── stm32_support.png ├── src ├── opamp.rs ├── fmac.rs ├── iwdg.rs ├── clocks │ └── mod.rs ├── can │ ├── g4.rs │ └── mod.rs ├── instant.rs ├── rng.rs ├── power.rs ├── macros.rs ├── error.rs ├── flash │ └── mod.rs ├── usb.rs ├── ethernet.rs ├── hsem.rs ├── usb_otg.rs └── spi │ └── mod.rs ├── LICENSE ├── .github └── workflows │ └── rust.yml └── Cargo.toml /examples/usb_otg_no_crs_rtic/.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | **/target 3 | Cargo.lock 4 | 5 | # IDE 6 | .idea -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | group_imports = "StdExternalCrate" -------------------------------------------------------------------------------- /screenshots/stm32_support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/David-OConnor/stm32-hal/HEAD/screenshots/stm32_support.png -------------------------------------------------------------------------------- /examples/blinky/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 512K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 96K 6 | } 7 | -------------------------------------------------------------------------------- /examples/sawtooth/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 512K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | } 7 | -------------------------------------------------------------------------------- /examples/conductivity_module/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 256K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 6 | } -------------------------------------------------------------------------------- /examples/syntax_overview/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 256K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 6 | } -------------------------------------------------------------------------------- /examples/blinky_timer_interrupt/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 564K 6 | } 7 | -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2022-10-09" 3 | components = [ "rust-src", "llvm-tools-preview", "rust-std"] 4 | targets = [ "thumbv7em-none-eabihf", "x86_64-unknown-linux-gnu" ] 5 | -------------------------------------------------------------------------------- /examples/blinky_timer_interrupt/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32F401CEUx" # to list chips, run `probe-run --list-chips.` 3 | 4 | [build] 5 | target = "thumbv7em-none-eabihf" 6 | 7 | [alias] 8 | rb = "run --bin" 9 | rrb = "run --release --bin" 10 | rr = "run --release" 11 | br = "build --release" -------------------------------------------------------------------------------- /src/opamp.rs: -------------------------------------------------------------------------------- 1 | //! Support for operational amplifiers. 2 | //! WIP / non-functional 3 | 4 | use core::ops::Deref; 5 | 6 | use crate::pac::opamp; 7 | 8 | /// Represents an operational amplifier peripheral. 9 | pub struct Opamp { 10 | pub regs: R, 11 | } 12 | 13 | impl Opamp 14 | where 15 | R: Deref, 16 | { 17 | pub fn new() -> Self { 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32L476ZGTx" 3 | 4 | rustflags = [ 5 | "-C", "linker=flip-link", 6 | "-C", "link-arg=-Tlink.x", 7 | "-C", "link-arg=-Tdefmt.x", 8 | "-C", "link-arg=--nmagic", 9 | ] 10 | 11 | [build] 12 | target = "thumbv7em-none-eabihf" 13 | 14 | [alias] 15 | rb = "run --bin" 16 | rrb = "run --release --bin" 17 | rr = "run --release" 18 | br = "build --release" -------------------------------------------------------------------------------- /examples/blinky/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32H743AGIx" # to list chips, run `probe-run --list-chips.` 3 | rustflags = [ 4 | "-C", "linker=flip-link", 5 | "-C", "link-arg=-Tlink.x", 6 | "-C", "link-arg=-Tdefmt.x", 7 | "-C", "link-arg=--nmagic", 8 | ] 9 | 10 | [build] 11 | target = "thumbv7em-none-eabihf" 12 | 13 | [alias] 14 | rb = "run --bin" 15 | rrb = "run --release --bin" 16 | rr = "run --release" 17 | br = "build --release" -------------------------------------------------------------------------------- /examples/sawtooth/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32G474RETx" # to list chips, run `probe-run --list-chips.` 3 | 4 | rustflags = [ 5 | "-C", "linker=flip-link", 6 | "-C", "link-arg=-Tlink.x", 7 | "-C", "link-arg=-Tdefmt.x", 8 | "-C", "link-arg=--nmagic", 9 | ] 10 | 11 | [build] 12 | target = "thumbv7em-none-eabihf" 13 | 14 | [alias] 15 | rb = "run --bin" 16 | rrb = "run --release --bin" 17 | rr = "run --release" 18 | br = "build --release" -------------------------------------------------------------------------------- /examples/conductivity_module/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32L443CCTx" # to list chips, run `probe-run --list-chips.` 3 | 4 | rustflags = [ 5 | "-C", "linker=flip-link", 6 | "-C", "link-arg=-Tlink.x", 7 | "-C", "link-arg=-Tdefmt.x", 8 | "-C", "link-arg=--nmagic", 9 | ] 10 | 11 | [build] 12 | target = "thumbv7em-none-eabihf" 13 | 14 | [alias] 15 | rb = "run --bin" 16 | rrb = "run --release --bin" 17 | rr = "run --release" 18 | br = "build --release" -------------------------------------------------------------------------------- /examples/syntax_overview/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | # runner = "probe-rs run --chip STM32L476RGTx" # to list chips, run `probe-run --list-chips. 3 | runner = "probe-rs run --chip STM32L443CCTx" # to list chips, run `probe-run --list-chips.` 4 | 5 | rustflags = [ 6 | "-C", "linker=flip-link", 7 | "-C", "link-arg=-Tlink.x", 8 | "-C", "link-arg=-Tdefmt.x", 9 | "-C", "link-arg=--nmagic", 10 | ] 11 | 12 | [build] 13 | target = "thumbv7em-none-eabihf" 14 | 15 | [alias] 16 | rb = "run --bin" 17 | rrb = "run --release --bin" 18 | rr = "run --release" 19 | br = "build --release" -------------------------------------------------------------------------------- /src/fmac.rs: -------------------------------------------------------------------------------- 1 | //! This module supports the Filter Match ACcelerator (FMAC) peripheral, which 2 | //! allows for hardware processing of digital filters such as FIR and IIR. 3 | 4 | // todo: Is this fixed point only? 5 | 6 | use crate::pac::{FMAC}; 7 | 8 | pub struct Fmac { 9 | pub regs: FMAC, 10 | } 11 | 12 | impl Fmac { 13 | /// Create a struct used to perform operations on Flash. 14 | pub fn new(regs: FMAC) -> Self { 15 | // todo: Implement and configure dual bank mode. 16 | Self { regs } 17 | } 18 | 19 | /// Set up a Finite Impulse Response (FIR) filter. 20 | fn run_fir(&mut self, coeffs: &[f32], data: &mut [f32]) { 21 | 22 | } 23 | 24 | /// Set up an Infinite Impulse Response (IIR) filter. 25 | fn run_iir(&mut self, coeffs: &[f32], data: &mut [f32]) { 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/abe6ea8ac11de69b7708ca9c70e8cd007600cd73.tar.gz) {} }: 2 | 3 | let 4 | rust_overlay = import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"); 5 | pkgs = import (builtins.fetchTarball https://github.com/NixOS/nixpkgs/archive/abe6ea8ac11de69b7708ca9c70e8cd007600cd73.tar.gz) { overlays = [ rust_overlay ]; }; 6 | in 7 | pkgs.mkShell { 8 | # buildInputs is for dependencies you'd need "at run time", 9 | # were you to to use nix-build not nix-shell and build whatever you were working on 10 | buildInputs = with pkgs; [ 11 | (rust-bin.fromRustupToolchainFile ./rust-toolchain.toml) 12 | flip-link 13 | rust-analyzer 14 | probe-run 15 | stlink 16 | stlink-gui 17 | usbutils 18 | cargo-binutils 19 | ]; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x8000000, LENGTH = 1024K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 96K 6 | } 7 | 8 | /* This is where the call stack will be allocated. */ 9 | /* The stack is of the full descending type. */ 10 | /* You may want to use this variable to locate the call stack and static 11 | variables in different memory regions. Below is shown the default value */ 12 | /* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ 13 | 14 | /* You can use this symbol to customize the location of the .text section */ 15 | /* If omitted the .text section will be placed right after the .vector_table 16 | section */ 17 | /* This is required only on microcontrollers that store some configuration right 18 | after the vector table */ 19 | /* _stext = ORIGIN(FLASH) + 0x400; */ 20 | 21 | /* Size of the heap (in bytes) */ 22 | /* _heap_size = 1024; */ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 David O'Connor 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sean Link "] 3 | name = "usb_otg_no_crs_rtic" 4 | edition = "2018" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] } 9 | cortex-m-rt = "0.7.0" 10 | cortex-m-rtic = "1.1.3" 11 | defmt = "0.3.0" 12 | defmt-rtt = "0.4.0" 13 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 14 | stm32-hal2 = {path = "../../", features = ["l4x6", "usbotg_fs"]} 15 | stm32-usbd = "0.6.0" 16 | usb-device = "0.2.9" 17 | usbd-serial = "0.1.1" 18 | fugit = "0.3.6" 19 | 20 | # cargo build/run 21 | [profile.dev] 22 | codegen-units = 1 23 | debug = 2 24 | debug-assertions = true # <- 25 | incremental = false 26 | opt-level = 3 # <- 27 | overflow-checks = true # <- 28 | 29 | # cargo build/run --release 30 | [profile.release] 31 | codegen-units = 1 32 | debug = 2 33 | debug-assertions = false # <- 34 | incremental = false 35 | # NOTE disabled to work around issue rust-lang/rust#90357 36 | # the bug results in log messages not having location information 37 | # (the line printed below the log message that contains the file-line location) 38 | # lto = 'fat' 39 | opt-level = 3 # <- 40 | overflow-checks = false # <- 41 | 42 | -------------------------------------------------------------------------------- /examples/blinky/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blinky" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | defmt = "0.3.0" 10 | defmt-rtt = "0.4.0" 11 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 12 | 13 | cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] } 14 | cortex-m-rt = "0.7.3" 15 | hal = { package = "stm32-hal2", version = "^1.9.0", features = ["f401", "f4rt"]} 16 | 17 | # cargo build/run 18 | [profile.dev] 19 | codegen-units = 1 20 | debug = 2 21 | debug-assertions = true # <- 22 | incremental = false 23 | opt-level = 3 # <- 24 | overflow-checks = true # <- 25 | 26 | # cargo test 27 | [profile.test] 28 | codegen-units = 1 29 | debug = 2 30 | debug-assertions = true # <- 31 | incremental = false 32 | opt-level = 3 # <- 33 | overflow-checks = true # <- 34 | 35 | # cargo build/run --release 36 | [profile.release] 37 | codegen-units = 1 38 | debug = 2 39 | debug-assertions = false # <- 40 | incremental = false 41 | lto = 'fat' 42 | opt-level = 3 # <- 43 | overflow-checks = false # <- 44 | 45 | # cargo test --release 46 | [profile.bench] 47 | codegen-units = 1 48 | debug = 2 49 | debug-assertions = false # <- 50 | incremental = false 51 | lto = 'fat' 52 | opt-level = 3 # <- 53 | overflow-checks = false # <- -------------------------------------------------------------------------------- /examples/sawtooth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sawtooth" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | defmt = "0.3.0" 10 | defmt-rtt = "0.4.0" 11 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 12 | 13 | cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] } 14 | cortex-m-rt = "0.7.0" 15 | hal = { package = "stm32-hal2", path = "../..", features = ["g474", "g4rt"]} 16 | cortex-m-rtic = "^1.1.3" 17 | 18 | # cargo build/run 19 | [profile.dev] 20 | codegen-units = 1 21 | debug = 2 22 | debug-assertions = true # <- 23 | incremental = false 24 | opt-level = 3 # <- 25 | overflow-checks = true # <- 26 | 27 | # cargo test 28 | [profile.test] 29 | codegen-units = 1 30 | debug = 2 31 | debug-assertions = true # <- 32 | incremental = false 33 | opt-level = 3 # <- 34 | overflow-checks = true # <- 35 | 36 | # cargo build/run --release 37 | [profile.release] 38 | codegen-units = 1 39 | debug = 2 40 | debug-assertions = false # <- 41 | incremental = false 42 | lto = 'fat' 43 | opt-level = 3 # <- 44 | overflow-checks = false # <- 45 | 46 | # cargo test --release 47 | [profile.bench] 48 | codegen-units = 1 49 | debug = 2 50 | debug-assertions = false # <- 51 | incremental = false 52 | lto = 'fat' 53 | opt-level = 3 # <- 54 | overflow-checks = false # <- -------------------------------------------------------------------------------- /examples/blinky_timer_interrupt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blinky_timer_interrupt" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | defmt = "0.3.0" 10 | defmt-rtt = "0.4.0" 11 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 12 | 13 | cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] } 14 | cortex-m-rt = "0.7.0" 15 | hal = { package = "stm32-hal2", version = "^1.9.0", features = ["h735", "h7rt"]} 16 | cortex-m-rtic = "^1.1.3" 17 | 18 | # cargo build/run 19 | [profile.dev] 20 | codegen-units = 1 21 | debug = 2 22 | debug-assertions = true # <- 23 | incremental = false 24 | opt-level = 3 # <- 25 | overflow-checks = true # <- 26 | 27 | # cargo test 28 | [profile.test] 29 | codegen-units = 1 30 | debug = 2 31 | debug-assertions = true # <- 32 | incremental = false 33 | opt-level = 3 # <- 34 | overflow-checks = true # <- 35 | 36 | # cargo build/run --release 37 | [profile.release] 38 | codegen-units = 1 39 | debug = 2 40 | debug-assertions = false # <- 41 | incremental = false 42 | lto = 'fat' 43 | opt-level = 3 # <- 44 | overflow-checks = false # <- 45 | 46 | # cargo test --release 47 | [profile.bench] 48 | codegen-units = 1 49 | debug = 2 50 | debug-assertions = false # <- 51 | incremental = false 52 | lto = 'fat' 53 | opt-level = 3 # <- 54 | overflow-checks = false # <- -------------------------------------------------------------------------------- /examples/syntax_overview/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["David O'Connor "] 3 | name = "syntax_overview" 4 | edition = "2018" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | defmt = "0.3.0" 9 | defmt-rtt = "0.4.0" 10 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 11 | 12 | cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] } 13 | cortex-m-rt = "0.7.3" 14 | # When importing this HAL, make sure to include a feature describing your MCU `eg l4x3`, 15 | # and if this program is meant to be run and flashed directly (ie it's not a library), 16 | # the runtime feature for that 17 | # family, eg `l4rt`. 18 | hal = { package = "stm32-hal2", version = "^1.9.0", features = ["l4x3", "l4rt"]} 19 | 20 | # cargo build/run 21 | [profile.dev] 22 | codegen-units = 1 23 | debug = 2 24 | debug-assertions = true # <- 25 | incremental = false 26 | opt-level = 3 # <- 27 | overflow-checks = true # <- 28 | 29 | # cargo test 30 | [profile.test] 31 | codegen-units = 1 32 | debug = 2 33 | debug-assertions = true # <- 34 | incremental = false 35 | opt-level = 3 # <- 36 | overflow-checks = true # <- 37 | 38 | # cargo build/run --release 39 | [profile.release] 40 | codegen-units = 1 41 | debug = 2 42 | debug-assertions = false # <- 43 | incremental = false 44 | lto = 'fat' 45 | opt-level = 3 # <- 46 | overflow-checks = false # <- 47 | 48 | # cargo test --release 49 | [profile.bench] 50 | codegen-units = 1 51 | debug = 2 52 | debug-assertions = false # <- 53 | incremental = false 54 | lto = 'fat' 55 | opt-level = 3 # <- 56 | overflow-checks = false # <- -------------------------------------------------------------------------------- /examples/conductivity_module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anyleaf_ec_firmware" 3 | version = "0.1.0" 4 | authors = ["David O'Connor "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "anyleaf_ec_firmware" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "anyleaf_ec_firmware" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] } 17 | cortex-m-rt = { version = "0.7.0", optional = true } 18 | hal = { package = "stm32-hal2", version = "^1.9.0", features = ["l4x3", "l4rt"] } 19 | defmt = "0.3.0" 20 | defmt-rtt = "0.4.0" 21 | panic-probe = { version = "0.4.0", features = ["print-defmt"] } 22 | 23 | [features] 24 | standalone = ["cortex-m", "cortex-m-rt", "stm32-hal2/l4rt"] 25 | 26 | # cargo build/run 27 | [profile.dev] 28 | codegen-units = 1 29 | debug = 2 30 | debug-assertions = true # <- 31 | incremental = false 32 | opt-level = 3 # <- 33 | overflow-checks = true # <- 34 | 35 | # cargo test 36 | [profile.test] 37 | codegen-units = 1 38 | debug = 2 39 | debug-assertions = true # <- 40 | incremental = false 41 | opt-level = 3 # <- 42 | overflow-checks = true # <- 43 | 44 | # cargo build/run --release 45 | [profile.release] 46 | codegen-units = 1 47 | debug = 2 48 | debug-assertions = false # <- 49 | incremental = false 50 | lto = 'fat' 51 | opt-level = 3 # <- 52 | overflow-checks = false # <- 53 | 54 | # cargo test --release 55 | [profile.bench] 56 | codegen-units = 1 57 | debug = 2 58 | debug-assertions = false # <- 59 | incremental = false 60 | lto = 'fat' 61 | opt-level = 3 # <- 62 | overflow-checks = false # <- -------------------------------------------------------------------------------- /examples/conductivity_module/README.md: -------------------------------------------------------------------------------- 1 | # Conductivity module firmware 2 | 3 | This is the entry point for firmware for the 4 | [AnyLeaf conductivity](https://www.anyleaf.org/ec-module) module. This device connects 5 | to an electrical conductivity probe, and passes readings to a host over a UART serial 6 | connection. (Example host: Raspberry Pi) It uses a simplified version of Analog Device's 7 | [CN0411 reference design](https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/cn0411.html), 8 | replacing some of the analog circuitry with software, and adapted for use with an STM32. 9 | 10 | Code in `ec.rs` is also used as a library for the same conductivity circuitry in the AnyLeaf 11 | Water Monitor. 12 | 13 | 14 | ## MCU selection 15 | This is set up for use with an STM32L443, but can be adapted to other STM32s, mainly by changing 16 | the MCU-specific lines in `Cargo.toml`, `memory.x`, `.cargo/config`. As standalone firmware, 17 | it may be more suitable for use on a simpler, low-cost device like Stm32G0 or Stm32G4. 18 | The main requirement limiting device selection is presense of a DAC. When switching MCUs, 19 | you will likely also need to change the relevant GPIO pin config. 20 | 21 | 22 | ## Flashing 23 | Make sure you have `flip-link` and `probe-run` installed, using the 24 | [instructions here](https://github.com/knurling-rs/app-template). 25 | 26 | To flash, run `cargo b --release --features="standalone"`, or create a binary with `cargo-binutils` 27 | using `cargo objcopy --release -- -O binary target/firmware.bin`, then flash with a tool of your 28 | choice, eg dfu-util, or Stm32CubeProgrammer. -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/README.md: -------------------------------------------------------------------------------- 1 | # USB OTG No CRS RTIC 2 | ## Why this example exists 3 | Not all stm32 devices have a Clock Recovery System (CRS) such as the stm32l476. 4 | For these device families, we can't use the CRS or clocks that depend on the 5 | CRS such as the HSI. See 6 | https://github.com/David-OConnor/stm32-hal/issues/64 for further details. 7 | 8 | The following example was run and tested on the STM32L476ZGTx varient. 9 | 10 | ## Running the example for the STM32L476ZGTx varient 11 | 1. Install the nix package manager by following the instructions [here](https://nixos.org/download.html). 12 | 2. Create a udev rule for the stm32 stlink following the instructions [here](https://docs.rust-embedded.org/discovery/f3discovery/03-setup/linux.html#create-etcudevrulesd99-openocdrules) 13 | 3. Reboot your machine so all OS modifactions take effect. 14 | 4. Enter the development environment subshell by navigating the the directory 15 | containing this readme and enter `nix-shell --pure` in the command line. 16 | > Note: You may find the development experience within the nix-shell to be 17 | > enjoyable if you drop the --pure option, however, running nix-shell without 18 | > the --pure option is not garunteed to work. Development is also greatly 19 | > improved by using direnv as used in [this](https://nix.dev/tutorials/declarative-and-reproducible-developer-environments?highlight=direnv#direnv-automatically-activating-the-environment-on-directory-change) post. 20 | 5. Compile the code by running `cargo build --bin ` a.k.a `cargo b 21 | --bin ` 22 | 6. Compile, Flash, and Run the code on the target device by running `cargo run 23 | --bin ` 24 | a.k.a `cargo r --bin ` 25 | 26 | ## Files needing modification to run on other stm32 varients 27 | 1. `.cargo/config.toml`. Modify target and chip. 28 | 2. `src/main.rs`. Modify the usb_dm and usb_dp pins. 29 | -------------------------------------------------------------------------------- /examples/blinky/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This minimial example causes an LED to blink using a (blocking) systick delay. It's 2 | //! the canonical "Hello world" of embedded programming. It demonstrates project structure, 3 | //! printing text to the console, using systick delays, and setting GPIO state. 4 | 5 | #![no_std] 6 | #![no_main] 7 | 8 | use cortex_m::delay::Delay; 9 | use cortex_m_rt::entry; // The runtime 10 | 11 | use hal::{ 12 | self, 13 | clocks::{Clocks}, 14 | gpio::{Pin, PinMode, Port}, 15 | pac, 16 | }; 17 | 18 | use defmt_rtt as _; 19 | // global logger 20 | use panic_probe as _; 21 | 22 | // This marks the entrypoint of our application. 23 | 24 | #[entry] 25 | fn main() -> ! { 26 | // Set up CPU peripherals 27 | let cp = cortex_m::Peripherals::take().unwrap(); 28 | // Set up microcontroller peripherals 29 | let mut dp = pac::Peripherals::take().unwrap(); 30 | 31 | defmt::println!("Hello, world!"); 32 | 33 | let clock_cfg = Clocks::default(); 34 | 35 | // Write the clock configuration to the MCU. If you wish, you can modify `clock_cfg` above 36 | // in accordance with [its docs](https://docs.rs/stm32-hal2/latest/stm32_hal2/clocks/index.html), 37 | // and the `clock_cfg` example. 38 | clock_cfg.setup().unwrap(); 39 | 40 | // Setup a delay, based on the Cortex-m systick. 41 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 42 | let mut led = Pin::new(Port::C, 13, PinMode::Output); 43 | 44 | loop { 45 | led.set_low(); 46 | defmt::debug!("Output pin is low."); 47 | delay.delay_ms(1_000); 48 | led.set_high(); 49 | defmt::debug!("Output pin is high."); 50 | delay.delay_ms(1_000); 51 | } 52 | } 53 | 54 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 55 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 56 | #[defmt::panic_handler] 57 | fn panic() -> ! { 58 | cortex_m::asm::udf() 59 | } 60 | -------------------------------------------------------------------------------- /src/iwdg.rs: -------------------------------------------------------------------------------- 1 | //! Indepdent watchdog 2 | 3 | #[cfg(not(any(feature = "h735", feature = "h747cm4", feature = "h747cm7")))] 4 | use crate::pac::IWDG; 5 | #[cfg(any(feature = "h735", feature = "h747cm4", feature = "h747cm7"))] 6 | use crate::pac::IWDG1 as IWDG; 7 | use crate::{ 8 | error::{Error, Result}, 9 | util::bounded_loop, 10 | }; 11 | 12 | const IWDG_CLOCK: f32 = 32_000.; 13 | 14 | /// Set up (enable), without window option. `timeout` is in seconds. 15 | /// G4 RM, section 42.3.2 16 | pub fn setup(timeout: f32) -> Result<()> { 17 | unsafe { 18 | let regs = &(*IWDG::ptr()); 19 | // When the window option it is not used, the IWDG can be configured as follows: 20 | // 1. Enable the IWDG by writing 0x0000 CCCC in the IWDG key register (IWDG_KR). 21 | regs.kr().write(|w| w.bits(0x0000_cccc)); 22 | 23 | // 2.Enable register access by writing 0x0000 5555 in the IWDG key register (IWDG_KR). 24 | regs.kr().write(|w| w.bits(0x0000_5555)); 25 | 26 | // 3. Write the prescaler by programming the IWDG prescaler register (IWDG_PR) from 0 to 27 | 28 | // 32kHz clock. 29 | // todo: Hardcoded prescaler of 32. This allows a timeout between 0 and 4.096 seconds. 30 | regs.pr().write(|w| w.bits(0b011)); 31 | 32 | // 4. Write the IWDG reload register (IWDG_RLR). 33 | // A 12-bit value. Assumes a prescaler of 32. 34 | let ticks_per_s = IWDG_CLOCK / 32.; 35 | let reload_val = (ticks_per_s * timeout) as u16; 36 | 37 | regs.rlr().write(|w| w.bits(reload_val)); 38 | 39 | // 5. Wait for the registers to be updated (IWDG_SR = 0x0000 0000). 40 | bounded_loop!(regs.sr().read().bits() != 0, Error::RegisterUnchanged); 41 | 42 | // 6. Refresh the counter value with IWDG_RLR (IWDG_KR = 0x0000 AAAA). 43 | pet(); 44 | 45 | Ok(()) 46 | } 47 | } 48 | 49 | /// Run this at an interval shorter than the countdown time to prevent a reset. 50 | pub fn pet() { 51 | unsafe { 52 | let regs = &(*IWDG::ptr()); 53 | regs.kr().write(|w| w.bits(0x0000_aaaa)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/clocks/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains clock configurations for various MCUs. They tend to be significantly 2 | //! different from one another, so we've feature-gated these files, rather than 3 | //! code within the files, to differentiate families. This documentation is built for H723, and will 4 | //! not be correct for other variants. For F series defaults, check out the [Default impl here](https://github.com/David-OConnor/stm32-hal/blob/main/src/clocks/f). 5 | //! [Check here for other H7 variants](https://github.com/David-OConnor/stm32-hal/blob/main/src/clocks/h7.rs) For other 6 | //! STM32 families, [look here](https://github.com/David-OConnor/stm32-hal/blob/main/src/clocks/baseline.rs). 7 | //! 8 | //! Alternatively, you can examine the `CLocks` structure to see which scalers are set, or generate docs locally 9 | //! for your variant.//! 10 | //! 11 | //! See STM32CubeIDE for an interactive editor that's very useful for seeing what 12 | //! settings are available, and validating them. 13 | //! 14 | //! See the Reference Manuals for non-interactive visualizations. 15 | 16 | cfg_if::cfg_if! { 17 | if #[cfg(feature = "f")] { 18 | mod f; 19 | pub use f::*; 20 | } else if #[cfg(any(feature = "l4", feature = "l5", feature = "g0", feature = "g4", feature = "wb", feature = "wl", feature = "c0"))] { 21 | mod baseline; 22 | pub use baseline::*; 23 | }else if #[cfg(any(feature = "h5", feature = "h7"))] { 24 | mod h; 25 | pub use h::*; 26 | } 27 | } 28 | 29 | // todo: Consider merging the modules into a single file: There's more similar than different. 30 | // todo: You have a good deal of DRY atm between modules. 31 | 32 | // Dat structures and functions that are shared between clock modules go here. 33 | 34 | // todo: Continue working through DRY between the clock modules. 35 | 36 | /// Speed out of limits. 37 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 38 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 39 | pub enum RccError { 40 | Speed, 41 | } 42 | 43 | // #[derive(Clone, Copy)] 44 | // #[repr(u8)] 45 | // pub enum ClocksValid { 46 | // Valid, 47 | // NotValid, 48 | // } 49 | -------------------------------------------------------------------------------- /src/can/g4.rs: -------------------------------------------------------------------------------- 1 | use fdcan; 2 | 3 | #[cfg(not(feature = "g431"))] 4 | use crate::pac::{FDCAN2, FDCAN3}; 5 | use crate::{ 6 | pac::{FDCAN1, RCC}, 7 | util::rcc_en_reset, 8 | }; 9 | 10 | const MESSAGE_RAM_BASE_ADDRESS: u32 = 0x4000_a400; 11 | 12 | // The RM is a bit contradictory. Table 3 implies that each FDCAN memory block is 0x400 in size. 13 | // But section 44.3.3 says each block is 0x350 and that is what actually works. 14 | const MESSAGE_RAM_SIZE: u32 = 0x350; 15 | 16 | const FDCAN1_MESSAGE_RAM_ADDRESS: u32 = MESSAGE_RAM_BASE_ADDRESS; 17 | const FDCAN2_MESSAGE_RAM_ADDRESS: u32 = FDCAN1_MESSAGE_RAM_ADDRESS + MESSAGE_RAM_SIZE; 18 | const FDCAN3_MESSAGE_RAM_ADDRESS: u32 = FDCAN2_MESSAGE_RAM_ADDRESS + MESSAGE_RAM_SIZE; 19 | 20 | macro_rules! create_cans { 21 | ($can:ident, $fdcan:ident, $msg_ram_address:ident ) => { 22 | /// Interface to the CAN peripheral. 23 | pub struct $can { 24 | pub regs: $fdcan, 25 | } 26 | impl $can { 27 | /// Initialize a CAN peripheral, including enabling and resetting 28 | /// its RCC peripheral clock. This is not handled by the `bxcan` or `canfd` crates. 29 | pub fn new(regs: $fdcan) -> Self { 30 | let rcc = unsafe { &*RCC::ptr() }; 31 | 32 | rcc_en_reset!(apb1, fdcan, rcc); 33 | 34 | Self { regs } 35 | } 36 | 37 | /// Print the (raw) contents of the status register. 38 | pub fn read_status(&self) -> u32 { 39 | unsafe { self.regs.psr().read().bits() } 40 | } 41 | } 42 | unsafe impl fdcan::Instance for $can { 43 | const REGISTERS: *mut fdcan::RegisterBlock = $fdcan::ptr() as *mut _; 44 | } 45 | 46 | unsafe impl fdcan::message_ram::Instance for $can { 47 | const MSG_RAM: *mut fdcan::message_ram::RegisterBlock = ($msg_ram_address as *mut _); 48 | } 49 | }; 50 | } 51 | 52 | create_cans!(Can, FDCAN1, FDCAN1_MESSAGE_RAM_ADDRESS); 53 | #[cfg(not(feature = "g431"))] 54 | create_cans!(Can2, FDCAN2, FDCAN2_MESSAGE_RAM_ADDRESS); 55 | #[cfg(not(feature = "g431"))] 56 | create_cans!(Can3, FDCAN3, FDCAN3_MESSAGE_RAM_ADDRESS); 57 | -------------------------------------------------------------------------------- /examples/blinky_timer_interrupt/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This minimial example causes an LED to blink using a (non-blocking) timer interrupt. 2 | //! It demonstrates basic GPIO use, timers, and RTIC project structure, with resources, and an interrupt handler. 3 | 4 | #![no_std] 5 | #![no_main] 6 | 7 | 8 | use cortex_m::asm; 9 | 10 | use hal::{ 11 | self, 12 | clocks::{Clocks}, 13 | gpio::{Pin, PinMode, Port}, 14 | timer::{Timer, TimerInterrupt}, 15 | pac::{self, TIM5}, 16 | }; 17 | 18 | use defmt_rtt as _; 19 | use panic_probe as _; 20 | 21 | const BLINK_FREQ: f32 = 1.; // seconds 22 | 23 | #[rtic::app(device = pac, peripherals = false)] 24 | mod app { 25 | // This line is required to allow imports inside an RTIC module. 26 | use super::*; 27 | 28 | #[shared] 29 | struct Shared {} 30 | 31 | #[local] 32 | struct Local { 33 | timer: Timer, 34 | led_pin: Pin, 35 | } 36 | 37 | #[init] 38 | fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { 39 | let mut dp = pac::Peripherals::take().unwrap(); 40 | 41 | let clock_cfg = Clocks::default(); 42 | clock_cfg.setup().unwrap(); 43 | 44 | let led_pin = Pin::new(Port::A, 1, PinMode::Output); 45 | 46 | let mut timer = Timer::new_tim5(dp.TIM5, BLINK_FREQ * 2., Default::default(), &clock_cfg); 47 | timer.enable_interrupt(TimerInterrupt::Update); 48 | timer.enable(); 49 | 50 | ( 51 | Shared {}, 52 | Local { timer, led_pin }, 53 | init::Monotonics(), 54 | ) 55 | } 56 | 57 | #[idle()] 58 | fn idle(cx: idle::Context) -> ! { 59 | loop { 60 | asm::nop(); 61 | } 62 | } 63 | 64 | #[task(binds = TIM5, local=[timer, led_pin], priority = 1)] 65 | /// When the timer's counter expires, toggle the pin connected to the LED. 66 | fn blink_isr(mut cx: blink_isr::Context) { 67 | cx.local.timer.clear_interrupt(TimerInterrupt::Update); 68 | // Or: timer::clear_update_interrupt(5); 69 | 70 | if cx.local.led_pin.is_low() { 71 | cx.local.led_pin.set_high(); 72 | } else { 73 | cx.local.led_pin.set_low(); 74 | } 75 | } 76 | } 77 | 78 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 79 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 80 | #[defmt::panic_handler] 81 | fn panic() -> ! { 82 | cortex_m::asm::udf() 83 | } 84 | -------------------------------------------------------------------------------- /src/instant.rs: -------------------------------------------------------------------------------- 1 | //! This module fits the requirement of `rtic-monotonic`, but has uses beyond that. 2 | 3 | use core::{ 4 | self, 5 | cmp::{Ord, Ordering, PartialOrd}, 6 | ops::{Add, Sub}, 7 | time::Duration, 8 | }; 9 | 10 | /// A time instant, from the start of a timer, with nanosecond precision. Has methods similar 11 | /// to that found in `core::Duration`. 12 | #[derive(Eq, PartialEq, PartialOrd, Copy, Clone, Default)] 13 | pub struct Instant { 14 | /// Total count, in microseconds. We use a signed integer for use with Durations. 15 | count_ns: i128, 16 | // todo: Count ticks instead? 17 | } 18 | 19 | /// An instant. Designed to be, in its most basic sense, similar to `std::time::Instant`. Created 20 | /// from the `now()` method on a `Timer`. Can be compared to create a `core::Duration`. Standalone 21 | /// methods on this struct are similar to those on `Duration`, but return timestamps since the timer start. 22 | impl Instant { 23 | pub(crate) fn new(count_ns: i128) -> Self { 24 | Self { count_ns } 25 | } 26 | 27 | /// The time, in seconds. 28 | pub fn as_secs(&self) -> f32 { 29 | self.count_ns as f32 / 1_000_000_000. 30 | } 31 | 32 | /// The time, in milliseconds. 33 | pub fn as_millis(&self) -> u32 { 34 | (self.count_ns / 1_000_000) as u32 35 | } 36 | 37 | /// The time, in milliseconds as an f32. 38 | pub fn as_millis_f32(&self) -> f32 { 39 | self.count_ns as f32 / 1_000_000. 40 | } 41 | 42 | /// The time, in microseconds 43 | pub fn as_micros(&self) -> u64 { 44 | (self.count_ns / 1_000) as u64 45 | } 46 | 47 | /// The time, in nanoseconds 48 | pub fn as_nanos(&self) -> u128 { 49 | self.count_ns as u128 50 | } 51 | } 52 | 53 | impl Ord for Instant { 54 | fn cmp(&self, other: &Self) -> Ordering { 55 | // self.count_us.cmp(&other.count_us) 56 | self.count_ns.cmp(&other.count_ns) 57 | } 58 | } 59 | 60 | impl Add for Instant { 61 | type Output = Self; 62 | 63 | fn add(self, rhs: Duration) -> Self::Output { 64 | Self { 65 | count_ns: self.count_ns + rhs.as_nanos() as i128, 66 | } 67 | } 68 | } 69 | 70 | impl Sub for Instant { 71 | type Output = Self; 72 | 73 | fn sub(self, rhs: Duration) -> Self::Output { 74 | Self { 75 | count_ns: self.count_ns - rhs.as_nanos() as i128, 76 | } 77 | } 78 | } 79 | 80 | impl Sub for Instant { 81 | type Output = Duration; 82 | 83 | fn sub(self, rhs: Self) -> Self::Output { 84 | // todo: Handle negative overflow. 85 | Duration::from_nanos((self.count_ns - rhs.count_ns) as u64) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/comp.rs: -------------------------------------------------------------------------------- 1 | //! This module includes an overview of Comparator features available. 2 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 3 | 4 | #![no_main] 5 | #![no_std] 6 | 7 | // These lines are part of our setup for debug printing. 8 | // Cortex-M Import 9 | use cortex_m::delay::Delay; 10 | use cortex_m_rt::entry; 11 | use defmt_rtt as _; 12 | // Importing library 13 | use hal::{ 14 | clocks::Clocks, 15 | comp::{self, Comp, CompConfig, CompDevice}, 16 | gpio::{Pin, PinMode, Port}, 17 | pac, 18 | }; 19 | use panic_probe as _; 20 | 21 | #[entry] 22 | fn main() -> ! { 23 | // Set up CPU peripherals 24 | let cp = cortex_m::Peripherals::take().unwrap(); 25 | // Set up microcontroller peripherals 26 | let _dp = pac::Peripherals::take().unwrap(); 27 | 28 | // Setting Up Clock 29 | let clock_cfg = Clocks::default(); 30 | clock_cfg.setup().unwrap(); 31 | 32 | // Setting Up GPIO (Not really needed) 33 | let _pin = Pin::new(Port::B, 2, PinMode::Analog); 34 | 35 | // Setting Up Delay 36 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 37 | 38 | // Setting Up Comparator 39 | // Comparator Configuration 40 | let cfg = CompConfig { 41 | // No Hysterysis 42 | hyst: comp::Hysterisis::NoHysterisis, 43 | // Using internal Vref as negative input 44 | // e.g. (1.22V) in STM32L47xx, STM32L48xx, STM32L49xx and STM32L4Axx. 45 | // Consult Reference Manual for all negative input. 46 | inmsel: comp::InvertingInput::Vref, 47 | // Using Io2 as positive input 48 | // e.g. (PB2) for COMP1 in STM32L47xx, STM32L48xx, STM32L49xx and STM32L4Axx. 49 | // Consult Reference Manual for all positive input. 50 | inpsel: comp::NonInvertingInput::Io2, 51 | // Don't invert output high when inverting input < noninverting and etc. 52 | polarity: comp::OutputPolarity::NotInverted, 53 | // High Power Consumption (lowest propagation delay) 54 | pwrmode: comp::PowerMode::HighSpeed, 55 | }; 56 | // Creating Comparator device using COMP1 57 | let mut comparator = Comp::new(CompDevice::One, cfg); 58 | // Starting Comparator 59 | comparator.start().unwrap(); 60 | 61 | loop { 62 | // Reading and Printing Output 63 | let output = comparator.get_output_level(); 64 | defmt::println!("{}", output); 65 | delay.delay_ms(1000u32); 66 | } 67 | } 68 | 69 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 70 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 71 | #[defmt::panic_handler] 72 | fn panic() -> ! { 73 | cortex_m::asm::udf() 74 | } 75 | -------------------------------------------------------------------------------- /examples/rtc.rs: -------------------------------------------------------------------------------- 1 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use core::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use cortex_m; 9 | use cortex_m_rt::entry; 10 | use critical_section::with; 11 | use hal::{ 12 | clocks::Clocks, 13 | low_power::{self, StopMode}, 14 | pac, 15 | prelude::*, 16 | rtc::{Rtc, RtcClockSource, RtcConfig}, 17 | setup_nvic, 18 | }; 19 | 20 | make_globals!((RTC, Rtc)); 21 | 22 | #[entry] 23 | fn main() -> ! { 24 | // Set up CPU peripherals 25 | let mut cp = cortex_m::Peripherals::take().unwrap(); 26 | // Set up microcontroller peripherals 27 | let mut dp = pac::Peripherals::take().unwrap(); 28 | 29 | let clock_cfg = Clocks::default(); 30 | 31 | clock_cfg.setup().unwrap(); 32 | 33 | // Set up the realtime clock. 34 | let mut rtc = Rtc::new( 35 | dp.RTC, 36 | RtcConfig { 37 | clock_source: RtcClockSource::Lse, 38 | bypass_lse_output: true, // eg if using a SMD oscillator. 39 | ..Default::default() 40 | }, 41 | ); 42 | 43 | rtc.set_12h_fmt(); // Optionally, use 12-hour format. 44 | 45 | // Set the RTC to trigger an interrupt every 30 seconds. 46 | rtc.set_wakeup(30.); 47 | 48 | // Store the RTC in a global variable that we can access in interrupts, using 49 | // critical sections. 50 | init_globals!((RTC, rtc)); 51 | 52 | // Unmask the interrupt line, and set its priority. 53 | setup_nvic!([(RTC_WKUP, 2),], cp); 54 | 55 | loop { 56 | // The RTC uses Chrono for dates, times, and datetimes. All of these are naive. 57 | let date = rtc.get_date(); 58 | let time = rtc.get_time(); 59 | let dt = rtc.get_datetime(); 60 | 61 | // See also: `get_seconds()`, `get_day()` etc. 62 | let hours = rtc.get_hours(); 63 | 64 | // Enter a low power mode. 65 | low_power::stop(StopMode::One); 66 | 67 | // Turn back on the PLL, which is disabled by setting `stop` mode. 68 | clock_cfg.reselect_input(); 69 | } 70 | } 71 | 72 | // todo: Alarms. 73 | 74 | #[interrupt] 75 | /// RTC wakeup handler 76 | fn RTC_WKUP() { 77 | with(|cs| { 78 | // Reset pending bit for interrupt line 79 | unsafe { 80 | (*pac::EXTI::ptr()).pr1.modify(|_, w| w.pr20().bit(true)); 81 | } 82 | access_global!(RTC, rtc, cs); 83 | rtc.clear_wakeup_flag(); 84 | 85 | // Do something. 86 | }); 87 | } 88 | 89 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 90 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 91 | #[defmt::panic_handler] 92 | fn panic() -> ! { 93 | cortex_m::asm::udf() 94 | } 95 | -------------------------------------------------------------------------------- /examples/flash.rs: -------------------------------------------------------------------------------- 1 | //! This module demonstrates basic reads and writes to and from onboard flash memory. 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use cortex_m_rt::entry; 7 | use critical_section::with; 8 | use hal::{ 9 | clocks::Clocks, 10 | flash::{Bank, Flash}, 11 | low_power, 12 | }; 13 | 14 | // Make sure the starting page isn't outside the available page range, and isn't in part of the 15 | // flash memory taken up by your program. If you're not sure what to do, put something towards the end. 16 | // Check your Reference Manual, FLASH section near the top for info on flash page size, and number 17 | // of pages. Called "sector" on H7 and F4. 18 | const FLASH_PAGE: usize = 254; 19 | 20 | #[entry] 21 | fn main() -> ! { 22 | // Set up CPU peripherals 23 | let mut cp = cortex_m::Peripherals::take().unwrap(); 24 | // Set up microcontroller peripherals 25 | let mut dp = pac::Peripherals::take().unwrap(); 26 | 27 | let clock_cfg = Clocks::Default(); 28 | clock_cfg.setup().unwrap(); 29 | 30 | let mut flash = Flash::new(dp.FLASH); 31 | 32 | // Before any writes take place, you must erase the page the writes will take place on. 33 | // On H7, replace `page` with `sector`. 34 | flash.erase_page(Bank::B1, FLASH_PAGE).ok(); 35 | 36 | // Note that if using L5, or some variants of G4 or L4, you can configure FLASH to operate in 37 | // dual bank mode by setting the DBANK option bit. This library won't set that for you, but you 38 | // can set it manually, then configure the `Flash` struct with the `dual_bank` field. By default, 39 | // it assumes single-bank config. Note that H7 is always dual bank, and other variants are always 40 | // single-bank. (F4 excepted) 41 | 42 | // Once a page is erased, we can write to it. Writes take place internally in 32-bit intervals 43 | // (H7 excepted; uses varying write sizes), but our API uses 8-bit buffers. 44 | flash.write_page(Bank::B1, FLASH_PAGE, &[1, 2, 3, 4]); 45 | 46 | // Alternatively, we can erase and write together using this function: 47 | flash.erase_write_page(Bank::B1, FLASH_PAGE, &[1, 2, 3, 4]); 48 | 49 | // We can read to a u8 buffer using the `read` method: 50 | let mut read_buf = [u8; 4]; 51 | flash.read(Bank::B1, FLASH_PAGE, 0, read_buf); 52 | 53 | // Note: Some debugging setups will crash during flash page erases or writes. You can check if 54 | // this is the case (after receieving an `SwdAPWait` message etc by inspecing the FLASH written 55 | // using `Stm32CubeProgrammer`, and verifying that your program is still running through other means. 56 | 57 | loop { 58 | low_power::sleep_now(); 59 | } 60 | } 61 | 62 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 63 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 64 | #[defmt::panic_handler] 65 | fn panic() -> ! { 66 | cortex_m::asm::udf() 67 | } 68 | -------------------------------------------------------------------------------- /src/rng.rs: -------------------------------------------------------------------------------- 1 | //! Support for the Random Number Generator (RNG) peripheral. This provides a simple 2 | //! API for accessing hardware-generated 32-bit random numbers, where constructing a `Rng` peripheral enables its 3 | //! peripheral clock, and provides some methods. 4 | //! Once this struct is constructed, the freestanding functions `read()`, and `reading_ready()` may be 5 | //! used to get a random number number, and check if a new one is available. 6 | 7 | use cfg_if::cfg_if; 8 | 9 | use crate::{ 10 | pac::{RCC, RNG}, 11 | util::rcc_en_reset, 12 | }; 13 | 14 | /// Represents a RNG peripheral. 15 | pub struct Rng { 16 | pub regs: RNG, 17 | } 18 | 19 | impl Rng { 20 | /// Initialize a RNG peripheral, including configuration register writes, and enabling and resetting 21 | /// its RCC peripheral clock. 22 | pub fn new(regs: RNG) -> Self { 23 | let rcc = unsafe { &(*RCC::ptr()) }; 24 | 25 | cfg_if! { 26 | if #[cfg(feature = "g0")] { 27 | rcc_en_reset!(ahb1, rng, rcc); 28 | } else if #[cfg(any(feature = "wb", feature = "wl"))] { 29 | rcc_en_reset!(ahb3, rng, rcc); 30 | } else { 31 | rcc_en_reset!(ahb2, rng, rcc); 32 | } 33 | } 34 | 35 | regs.cr().modify(|_, w| w.rngen().bit(true)); 36 | 37 | Self { regs } 38 | } 39 | 40 | /// Load a random number from the data register 41 | pub fn read(&mut self) -> i32 { 42 | // When data is not ready (DRDY=”0”) RNG_DR returns zero. 43 | // It is recommended to always verify that RNG_DR is different from zero. Because when it is 44 | // the case a seed error occurred between RNG_SR polling and RND_DR output reading (rare 45 | // event). 46 | // todo: CHeck for 0? Check for DREDY? 47 | 48 | self.regs.dr().read().bits() as i32 49 | } 50 | 51 | /// Return true if a reading is available. 52 | pub fn reading_ready(&mut self) -> bool { 53 | self.regs.sr().read().drdy().bit_is_set() 54 | } 55 | 56 | /// Enable an interrupt. An interrupt isgenerated when a random number is ready or when an error 57 | /// occurs. Therefore at each interrupt, check that: No error occured (SEIS and CEIS bits should be set 58 | /// to 0 in the RNG_SR register. A random number is ready. The DRDY bit must be set to 1 in the 59 | /// RNG_SR register. 60 | pub fn enable_interrupt(&mut self) { 61 | self.regs.cr().modify(|_, w| w.ie().bit(true)); 62 | } 63 | } 64 | 65 | /// Gets a random value without needing to pass the `Rng` struct. Assumes it has been initialized, 66 | /// to enable it and its peripheral clock. 67 | pub fn read() -> i32 { 68 | let regs = unsafe { &(*RNG::ptr()) }; 69 | regs.dr().read().bits() as i32 70 | } 71 | 72 | /// Return true if a reading is available. 73 | pub fn reading_ready() -> bool { 74 | let regs = unsafe { &(*RNG::ptr()) }; 75 | regs.sr().read().drdy().bit_is_set() 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | six: 14 | name: Thumbv6 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | mcu: 19 | - g030 20 | - g031 21 | - g041 22 | - g050 23 | - g051 24 | - g061 25 | - g070 26 | - g071 27 | - g081 28 | #- g0b0 29 | - g0b1 30 | - g0c1 31 | - c011 32 | - c031 33 | - c071 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: stable 40 | target: thumbv6m-none-eabi 41 | components: rust-src 42 | override: true 43 | - name: Build 44 | run: cargo build --features=${{ matrix.mcu }},usb --lib 45 | - name: Run tests 46 | run: cargo test --features=${{ matrix.mcu }},usb --lib 47 | 48 | seven: 49 | name: Thumbv7 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | mcu: 54 | - l4x1 55 | - l4x2 56 | - l412 57 | - l4x3 58 | - l4x5 59 | - l4x6 60 | - f301 61 | - f302 62 | - f303 63 | - f373 64 | - f3x4 65 | - f401 66 | - f405 67 | - f407 68 | - f410 69 | - f411 70 | - f412 71 | - f413 72 | - f427 73 | - f429 74 | - f446 75 | - f469 76 | - g431 77 | - g441 78 | - g471 79 | - g473 80 | - g474 81 | - g483 82 | - g484 83 | - g491 84 | - g4a1 85 | # -h503 86 | # -h562 87 | # -h563 88 | # -h573 89 | - h735 90 | - h743 91 | - h743v 92 | - h747cm4 93 | - h747cm7 94 | - h753 95 | - h753v 96 | # - h7b3 97 | - wb55 98 | - wle5 99 | 100 | steps: 101 | - uses: actions/checkout@v2 102 | - uses: actions-rs/toolchain@v1 103 | with: 104 | toolchain: stable 105 | target: thumbv7em-none-eabihf 106 | components: rust-src 107 | override: true 108 | - name: Build 109 | run: cargo build --features=${{ matrix.mcu }},usb --lib 110 | - name: Run tests 111 | run: cargo test --features=${{ matrix.mcu }},usb --lib 112 | 113 | eight: 114 | name: Thumbv8 115 | runs-on: ubuntu-latest 116 | strategy: 117 | matrix: 118 | mcu: 119 | - l552 120 | - l562 121 | 122 | steps: 123 | - uses: actions/checkout@v2 124 | - uses: actions-rs/toolchain@v1 125 | with: 126 | toolchain: stable 127 | target: thumbv8m.main-none-eabihf 128 | components: rust-src 129 | override: true 130 | - name: Build 131 | run: cargo build --features=${{ matrix.mcu }},usb --lib 132 | - name: Run tests 133 | run: cargo test --features=${{ matrix.mcu }},usb --lib -------------------------------------------------------------------------------- /examples/can-send.rs: -------------------------------------------------------------------------------- 1 | //! Simple CAN-bus send example, it is inspired to the example provided by stm32f4xx-hal 2 | //! Requires a transceiver connected to PB8, 9 (CAN1) 3 | 4 | #![no_std] 5 | #![no_main] 6 | 7 | use core::panic::PanicInfo; 8 | 9 | use bxcan::{Frame, StandardId, filter::Mask32}; 10 | use cortex_m::delay::Delay; 11 | use cortex_m_rt::entry; // The runtime 12 | use defmt_rtt as _; 13 | use hal::{ 14 | self, 15 | can::Can, 16 | clocks::{self, ApbPrescaler, Clocks, InputSrc, PllSrc, Pllp}, 17 | gpio::{OutputType, Pin, PinMode, Port}, 18 | pac, 19 | }; 20 | use nb::block; 21 | 22 | // This marks the entrypoint of our application. The cortex_m_rt creates some 23 | // startup code before this, but we don't need to worry about this 24 | #[entry] 25 | fn main() -> ! { 26 | // Get handles to the hardware objects. These functions can only be called 27 | // once, so that the borrowchecker can ensure you don't reconfigure 28 | // something by accident. 29 | let cp = cortex_m::Peripherals::take().unwrap(); 30 | let dp = pac::Peripherals::take().unwrap(); 31 | let mut rcc = dp.RCC; 32 | 33 | // this line is required if you want to take advantage of ST-Link 34 | hal::debug_workaround(); 35 | 36 | // Configure the clocks accordingly with your hardware [its docs](https://docs.rs/stm32-hal2/0.2.0/stm32_hal2/clocks/index.html). 37 | // In this configuration we have an external crystal of 8MHz. 38 | // CAN-bus peripheral is on APB1 bus, it is important to make it work properly and set the right bit timing! 39 | let clock_cfg = Clocks { 40 | input_src: InputSrc::Pll(PllSrc::Hse(8_000_000)), 41 | pllm: 8, 42 | plln: 180, 43 | pllp: Pllp::Div2, 44 | apb1_prescaler: ApbPrescaler::Div2, 45 | ..Default::default() 46 | }; 47 | 48 | // Write the clock configuration to the MCU. 49 | clock_cfg.setup().unwrap(); 50 | 51 | let mut can = { 52 | let mut _rx = Pin::new(Port::B, 8, PinMode::Alt(9)); 53 | let mut _tx = Pin::new(Port::B, 9, PinMode::Alt(9)); 54 | 55 | _rx.output_type(OutputType::PushPull); 56 | _tx.output_type(OutputType::PushPull); 57 | 58 | let can = Can::new(dp.CAN1, &mut rcc); 59 | 60 | bxcan::Can::builder(can) 61 | // APB1 (PCLK1): 45MHz, Bit rate: 1000kBit/s, Sample Point 86.7% 62 | // Value was calculated with http://www.bittiming.can-wiki.info/ 63 | .set_bit_timing(0x001b0002) 64 | .enable() 65 | }; 66 | 67 | defmt::info!("CAN enabled!"); 68 | 69 | let mut test: [u8; 8] = [0; 8]; 70 | let mut count: u8 = 0; 71 | let id: u16 = 0x500; 72 | 73 | test[1] = 1; 74 | test[2] = 2; 75 | test[3] = 3; 76 | test[4] = 4; 77 | test[5] = 5; 78 | test[6] = 6; 79 | test[7] = 7; 80 | let test_frame = Frame::new_data(StandardId::new(id).unwrap(), test); 81 | 82 | loop { 83 | test[0] = count; 84 | let test_frame = Frame::new_data(StandardId::new(id).unwrap(), test); 85 | block!(can.transmit(&test_frame)).unwrap(); 86 | if count < 255 { 87 | count += 1; 88 | } else { 89 | count = 0; 90 | } 91 | } 92 | } 93 | 94 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 95 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 96 | #[panic_handler] 97 | fn panic(_info: &PanicInfo) -> ! { 98 | cortex_m::asm::udf() 99 | } 100 | -------------------------------------------------------------------------------- /src/power.rs: -------------------------------------------------------------------------------- 1 | //! Manage STM32H7 supply configuration. This is required on some H7 variants, to specify 2 | //! which regulator to use. This must match the way the MCU power pins are wired on the hardware design. 3 | 4 | use crate::pac::PWR; 5 | 6 | #[derive(Clone, Copy)] 7 | #[repr(u8)] 8 | /// SMPS step-down converter voltage output level selection. 9 | /// This bit is used when both the LDO and SMPS step-down converter are enabled with SDEN and 10 | /// LDOEN enabled or when SDEXTHP is enabled. In this case SDLEVEL has to be written with a 11 | /// value different than 00 at system startup. 12 | pub enum VoltageLevel { 13 | /// 1.8V 14 | V1_8 = 0b01, 15 | /// 2.5V 16 | V2_5 = 0b10, 17 | } 18 | 19 | #[derive(Clone, Copy)] 20 | /// See RM0399, Table 32. Supply configuration control, for available configurations. 21 | /// Sets the PWR_CR3 register, LDOEN, SDEN, SDEXTHP, SDLEVEL, and BYPASS fields. 22 | pub enum SupplyConfig { 23 | /// Default configuration 24 | Default, 25 | /// LDO supply 26 | Ldo, 27 | /// Direct SMPS step-down converter supply 28 | DirectSmps, 29 | /// SMPS step-down converter supplies LDO 30 | SmpsStepdownLdo, 31 | /// SMPS step-down converter supplies External and LDO 32 | SmpsStepdownExtLdo, 33 | /// SMPS step-down converter supplies external and LDO Bypass 34 | SmpsStpdownExtBypass, 35 | /// SMPS step-down converter disabled and LDO Bypass 36 | SmpsStepdownDisabledBypass, 37 | } 38 | 39 | impl SupplyConfig { 40 | /// Apply a given supply config. `voltage_level` only affects certain variants. 41 | pub fn setup(&self, pwr: &mut PWR, voltage_level: VoltageLevel) { 42 | match self { 43 | Self::Default => pwr.cr3().modify(|_, w| unsafe { 44 | w.sdlevel().bits(voltage_level as u8); 45 | w.sdexthp().clear_bit(); 46 | w.sden().bit(true); 47 | w.ldoen().bit(true); 48 | w.bypass().clear_bit() 49 | }), 50 | Self::Ldo => pwr.cr3().modify(|_, w| unsafe { 51 | w.sden().clear_bit(); 52 | w.ldoen().bit(true); 53 | w.bypass().clear_bit() 54 | }), 55 | Self::DirectSmps => pwr.cr3().modify(|_, w| unsafe { 56 | w.sdexthp().clear_bit(); 57 | w.sden().bit(true); 58 | w.ldoen().clear_bit(); 59 | w.bypass().clear_bit() 60 | }), 61 | Self::SmpsStepdownLdo => pwr.cr3().modify(|_, w| unsafe { 62 | w.sdlevel().bits(voltage_level as u8); 63 | w.sdexthp().clear_bit(); 64 | w.sden().bit(true); 65 | w.ldoen().bit(true); 66 | w.bypass().clear_bit() 67 | }), 68 | Self::SmpsStepdownExtLdo => pwr.cr3().modify(|_, w| unsafe { 69 | w.sdlevel().bits(voltage_level as u8); 70 | w.sdexthp().bit(true); 71 | w.sden().bit(true); 72 | w.ldoen().bit(true); 73 | w.bypass().clear_bit() 74 | }), 75 | Self::SmpsStpdownExtBypass => pwr.cr3().modify(|_, w| unsafe { 76 | w.sdlevel().bits(voltage_level as u8); 77 | w.sdexthp().bit(true); 78 | w.sden().bit(true); 79 | w.ldoen().clear_bit(); 80 | w.bypass().bit(true) 81 | }), 82 | Self::SmpsStepdownDisabledBypass => pwr.cr3().modify(|_, w| unsafe { 83 | w.sden().clear_bit(); 84 | w.ldoen().clear_bit(); 85 | w.bypass().bit(true) 86 | }), 87 | }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/sawtooth/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This minimial example creates a sawtooth waveform on DAC1 OUT1 2 | //! TIM4 interrupt blinks an LED on PA5, and is used as a reset trigger source for the DAC 3 | //! TIM2 is configured to run at 1kHz, and is used as an increment trigger source for the DAC 4 | 5 | #![no_std] 6 | #![no_main] 7 | 8 | 9 | use cortex_m::asm; 10 | 11 | use hal::{ 12 | self, 13 | clocks::{Clocks}, 14 | gpio::{Pin, PinMode, Port}, 15 | timer::{Timer, BasicTimer, TimerInterrupt, MasterModeSelection}, 16 | pac::{self, TIM4, TIM2}, 17 | dac::{Dac, DacChannel, SawtoothConfig, SawtoothDirection, Trigger}, 18 | }; 19 | 20 | use defmt_rtt as _; 21 | use panic_probe as _; 22 | 23 | const BLINK_FREQ: f32 = 1. / 0.1; 24 | 25 | #[rtic::app(device = pac, peripherals = false)] 26 | mod app { 27 | // This line is required to allow imports inside an RTIC module. 28 | use super::*; 29 | 30 | #[shared] 31 | struct Shared {} 32 | 33 | #[local] 34 | struct Local { 35 | timer: Timer, 36 | inc_timer: Timer, 37 | led_pin: Pin, 38 | } 39 | 40 | #[init] 41 | fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { 42 | let mut dp = pac::Peripherals::take().unwrap(); 43 | 44 | let clock_cfg = Clocks::default(); 45 | clock_cfg.setup().unwrap(); 46 | 47 | let led_pin = Pin::new(Port::A, 5, PinMode::Output); 48 | 49 | let mut timer = Timer::new_tim4(dp.TIM4, BLINK_FREQ * 2., Default::default(), &clock_cfg); 50 | timer.enable_interrupt(TimerInterrupt::Update); 51 | 52 | // Enable TRGO from TIM4 to provide a reset source to the DAC Sawtooth wave generator 53 | timer.regs.cr2().modify(|_, w| unsafe { w.mms().bits(MasterModeSelection::Update as u8) }); 54 | timer.enable(); 55 | 56 | let mut inc_timer = Timer::new_tim2(dp.TIM2, 1000.0, Default::default(), &clock_cfg); 57 | 58 | // Enable TRGO from TIM2 to provide an increment source to the DAC Sawtooth wave generator 59 | inc_timer.regs.cr2().modify(|_, w| unsafe { w.mms().bits(MasterModeSelection::Update as u8) }); 60 | inc_timer.enable(); 61 | 62 | 63 | // Set DAC1 OUT1 pin PA4 to Analog 64 | Pin::new(Port::A, 4, PinMode::Analog); 65 | 66 | let mut dac1 = Dac::new(dp.DAC1, Default::default(), 3.3); 67 | 68 | let sawtooth_cfg = SawtoothConfig { 69 | increment_trigger: Trigger::Tim2, 70 | reset_trigger: Trigger::Tim4, 71 | initial: 0, 72 | increment: 1000, 73 | direction: SawtoothDirection::Rising 74 | }; 75 | 76 | dac1.generate_sawtooth(DacChannel::C1, sawtooth_cfg); 77 | dac1.enable(DacChannel::C1); 78 | 79 | ( 80 | Shared {}, 81 | Local { inc_timer, timer, led_pin }, 82 | init::Monotonics(), 83 | ) 84 | } 85 | 86 | #[idle()] 87 | fn idle(cx: idle::Context) -> ! { 88 | loop { 89 | asm::nop(); 90 | } 91 | } 92 | 93 | #[task(binds = TIM4, local=[timer, led_pin], priority = 1)] 94 | /// When the timer's counter expires, toggle the pin connected to the LED. 95 | fn blink_isr(mut cx: blink_isr::Context) { 96 | cx.local.timer.clear_interrupt(TimerInterrupt::Update); 97 | 98 | if cx.local.led_pin.is_low() { 99 | cx.local.led_pin.set_high(); 100 | } else { 101 | cx.local.led_pin.set_low(); 102 | } 103 | } 104 | 105 | #[task(binds = TIM2, local=[inc_timer], priority = 2)] 106 | fn inc_isr(mut cx: inc_isr::Context) { 107 | cx.local.inc_timer.clear_interrupt(TimerInterrupt::Update); 108 | defmt::println!("Inc"); 109 | } 110 | } 111 | 112 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 113 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 114 | #[defmt::panic_handler] 115 | fn panic() -> ! { 116 | cortex_m::asm::udf() 117 | } 118 | -------------------------------------------------------------------------------- /examples/usb_otg_no_crs_rtic/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Not all stm32 devices have a Clock Recovery System (CRS) such as the stm32l476. 5 | // For these device families, we can't use the CRS or clocks that depend on the 6 | // CRS such as the HSI. See 7 | // https://github.com/David-OConnor/stm32-hal/issues/64 for further details. 8 | // 9 | // The following example was run and tested on the STM32L476ZGTx varient. 10 | 11 | use defmt_rtt as _; 12 | use panic_probe as _; 13 | 14 | pub mod usb { 15 | use hal::pac::{PWR, RCC}; 16 | 17 | /// Enables VddUSB power supply 18 | pub fn enable_usb_pwr() { 19 | // Enable PWR peripheral 20 | let rcc = unsafe { &(*RCC::ptr()) }; 21 | rcc.apb1enr1().modify(|_, w| w.pwren().bit(true)); 22 | 23 | // Enable VddUSB 24 | let pwr = unsafe { &*PWR::ptr() }; 25 | pwr.cr2().modify(|_, w| w.usv().bit(true)); 26 | } 27 | } 28 | 29 | #[rtic::app(device = pac, dispatchers = [USART1])] 30 | mod app { 31 | 32 | use cortex_m::asm; 33 | 34 | use hal::{ 35 | self, 36 | clocks::Clocks, 37 | gpio::{Pin, PinMode, Port}, 38 | pac, 39 | }; 40 | 41 | use hal::clocks::Clk48Src; 42 | use hal::usb_otg::{Usb1, UsbBus}; 43 | 44 | use usb_device::prelude::*; 45 | 46 | use usbd_serial::SerialPort; 47 | use usbd_serial::USB_CLASS_CDC; 48 | 49 | use usb_device::class_prelude::UsbBusAllocator; 50 | 51 | use super::*; 52 | 53 | pub struct PeripheralUsb { 54 | pub serial: SerialPort<'static, UsbBus>, 55 | pub device: UsbDevice<'static, UsbBus>, 56 | } 57 | 58 | #[shared] 59 | struct Shared { 60 | peripheral_usb: PeripheralUsb, 61 | } 62 | 63 | #[local] 64 | struct Local {} 65 | 66 | #[init(local = [ 67 | usb_buf: [u32; 64] = [0; 64], 68 | usb_bus: Option>> = None, 69 | ])] 70 | fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { 71 | // Use the msi clock for the 48 Mhz input to the USB peripheral rather 72 | // than the hsi. 73 | let clock_cfg = Clocks { 74 | clk48_src: Clk48Src::Msi, 75 | ..Default::default() 76 | }; 77 | clock_cfg.setup().unwrap(); 78 | clock_cfg.enable_msi_48(); 79 | 80 | usb::enable_usb_pwr(); 81 | 82 | let _usb_dm = Pin::new(Port::A, 11, PinMode::Alt(10)); 83 | let _usb_dp = Pin::new(Port::A, 12, PinMode::Alt(10)); 84 | 85 | let usb1 = Usb1::new( 86 | cx.device.OTG_FS_GLOBAL, 87 | cx.device.OTG_FS_DEVICE, 88 | cx.device.OTG_FS_PWRCLK, 89 | clock_cfg.hclk(), 90 | ); 91 | let buf: &'static mut _ = cx.local.usb_buf; 92 | let bus: &'static mut _ = cx.local.usb_bus.insert(UsbBus::new(usb1, buf)); 93 | let serial = SerialPort::new(bus); 94 | let device = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd)) 95 | .manufacturer("Fake Company") 96 | .product("Serial Port") 97 | .serial_number("SN") 98 | .device_class(USB_CLASS_CDC) 99 | .build(); 100 | let peripheral_usb = PeripheralUsb { serial, device }; 101 | 102 | (Shared { peripheral_usb }, Local {}, init::Monotonics()) 103 | } 104 | 105 | #[idle()] 106 | fn idle(_cx: idle::Context) -> ! { 107 | usb_say_hello::spawn().unwrap(); 108 | loop { 109 | asm::nop() 110 | } 111 | } 112 | 113 | #[task(shared = [peripheral_usb])] 114 | fn usb_say_hello(cx: usb_say_hello::Context) { 115 | defmt::println!("usb_say_hello"); 116 | let mut peripheral_usb = cx.shared.peripheral_usb; 117 | 118 | peripheral_usb.lock(|PeripheralUsb { device, serial }| loop { 119 | if !device.poll(&mut [serial]) { 120 | continue; 121 | } 122 | 123 | // Something in the usb buffer. Process it. 124 | let mut buf = [0u8; 64]; 125 | 126 | match serial.read(&mut buf[..]) { 127 | Ok(count) => { 128 | // Echo back to the serial console. 129 | serial.write(&buf[..count]).unwrap(); 130 | } 131 | Err(UsbError::WouldBlock) => { 132 | // Add error handling 133 | } 134 | Err(_err) => { 135 | // Add error handling 136 | } 137 | } 138 | }) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | //! This module contains utility macros that are not STM32-specific, or specific 2 | //! to this library. 3 | 4 | /// Syntax helper for getting global variables of the form `Mutex>>` from an interrupt-free 5 | /// context - eg in interrupt handlers. 6 | /// 7 | /// Example: `access_global!(DELAY, delay, cs)` 8 | #[macro_export] 9 | macro_rules! access_global { 10 | ($NAME_GLOBAL:ident, $name_local:ident, $cs:expr) => { 11 | let mut part1 = $NAME_GLOBAL.borrow($cs).borrow_mut(); 12 | let $name_local = part1.as_mut().unwrap(); 13 | }; 14 | } 15 | 16 | /// Similar to `access_global`, but combining multiple calls. 17 | /// 18 | /// Example: `access_globals!([ 19 | /// (USB_DEV, usb_dev), 20 | /// (USB_SERIAL, usb_serial), 21 | /// ], cs);` 22 | #[macro_export] 23 | macro_rules! access_globals { 24 | ([$(($NAME_GLOBAL:ident, $name_local:ident)),* $(,)?], $cs:expr) => { 25 | $( 26 | let mut part = $NAME_GLOBAL.borrow($cs).borrow_mut(); 27 | let $name_local = part.as_mut().unwrap(); 28 | )* 29 | }; 30 | } 31 | 32 | /// Syntax helper for setting global variables of the form `Mutex>>`. 33 | /// eg in interrupt handlers. Ideal for non-copy-type variables that can't be initialized 34 | /// immediatiately. 35 | /// 36 | /// Example: `make_globals!( 37 | /// (USB_SERIAL, SerialPort), 38 | /// (DELAY, Delay), 39 | /// )` 40 | #[macro_export] 41 | macro_rules! make_globals { 42 | ($(($NAME:ident, $type:ty)),+ $(,)?) => { 43 | $( 44 | static $NAME: ::critical_section::Mutex>> = ::critical_section::Mutex::new(core::cell::RefCell::new(None)); 45 | )+ 46 | }; 47 | } 48 | 49 | /// Syntax helper for setting global variables of the form `Mutex>>`. 50 | /// eg in interrupt handlers. Ideal for copy-type variables. 51 | /// 52 | /// Example: `make_simple_globals!( 53 | /// (VALUE, f32, 2.), 54 | /// (SETTING, Setting, Setting::A), 55 | /// )` 56 | #[macro_export] 57 | macro_rules! make_simple_globals { 58 | ($(($NAME:ident, $type:ty, $val:expr)),+ $(,)?) => { 59 | $( 60 | static $NAME: ::critical_section::Mutex> = ::critical_section::Mutex::new(core::cell::Cell::new($val)); 61 | )+ 62 | }; 63 | } 64 | 65 | /// Initialize one or more globals inside a critical section. 66 | /// 67 | /// Usage: 68 | /// ```rust 69 | /// init_globals!( 70 | /// (FLASH, flash), 71 | /// (SPI_IMU, spi1), 72 | /// (I2C_MAG, i2c1), 73 | /// // … 74 | /// ); 75 | /// ``` 76 | #[macro_export] 77 | macro_rules! init_globals { 78 | ($(($NAME:ident, $val:expr)),* $(,)?) => { 79 | ::critical_section::with(|cs| { 80 | $( 81 | $NAME.borrow(cs).replace(Some($val)); 82 | )* 83 | }); 84 | }; 85 | } 86 | 87 | /// Automates Cortex-M NVIC setup. The second value is NVIC priority; lower 88 | /// is higher priority. Example use: 89 | /// setup_nvic!([ 90 | /// (TIM2, 6), 91 | /// (TIM3, 7), 92 | /// (EXTI0, 4), 93 | /// ], cp); 94 | #[macro_export] 95 | macro_rules! setup_nvic { 96 | ( 97 | [ $( ($int:ident, $prio:expr) ),* $(,)? ], 98 | $cp:ident 99 | ) => { 100 | unsafe { 101 | $( 102 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::$int); 103 | )* 104 | $( 105 | $cp.NVIC.set_priority(pac::Interrupt::$int, $prio); 106 | )* 107 | } 108 | }; 109 | } 110 | 111 | /// Syntax helper for parsing multi-byte fields into primitives. 112 | /// 113 | /// Example: `parse_le!(bytes, i32, 5..9);` 114 | #[macro_export] 115 | macro_rules! parse_le { 116 | ($bytes:expr, $t:ty, $range:expr) => {{ <$t>::from_le_bytes($bytes[$range].try_into().unwrap()) }}; 117 | } 118 | 119 | /// Syntax helper for parsing multi-byte fields into primitives. 120 | /// 121 | /// Example: `parse_be!(bytes, i32, 5..9);` 122 | #[macro_export] 123 | macro_rules! parse_be { 124 | ($bytes:expr, $t:ty, $range:expr) => {{ <$t>::from_be_bytes($bytes[$range].try_into().unwrap()) }}; 125 | } 126 | 127 | /// Syntax helper for converting primitives to multi-byte fields. 128 | /// 129 | /// Example: `copy_le!(bytes, self.position, 5..9);` 130 | #[macro_export] 131 | macro_rules! copy_le { 132 | ($dest:expr, $src:expr, $range:expr) => {{ $dest[$range].copy_from_slice(&$src.to_le_bytes()) }}; 133 | } 134 | 135 | /// Syntax helper for converting primitives to multi-byte fields. 136 | /// 137 | /// Example: `copy_be!(bytes, self.position, 5..9);` 138 | #[macro_export] 139 | macro_rules! copy_be { 140 | ($dest:expr, $src:expr, $range:expr) => {{ $dest[$range].copy_from_slice(&$src.to_be_bytes()) }}; 141 | } 142 | -------------------------------------------------------------------------------- /examples/uart.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to use interrupts to read and write UART (serial) 2 | //! data. We take advantage of global static Mutexes as buffers that can be accessed 3 | //! from interrupt concept to read and write data as it's ready, allowing the CPU to 4 | //! perform other tasks while waiting. 5 | //! 6 | //! Note: For many cases when reading or writing multiple words, DMA should be the 7 | //! first choice, to minimize CPU use. 8 | 9 | #![no_main] 10 | #![no_std] 11 | 12 | use cortex_m_rt::entry; 13 | use critical_section::with; 14 | use hal::{ 15 | clocks::Clocks, 16 | dma::{self, Dma, DmaChannel, DmaConfig, DmaPeriph}, 17 | gpio::{Pin, PinMode, Port}, 18 | low_power, 19 | pac::{self, interrupt}, 20 | prelude::*, 21 | usart::{Usart, UsartConfig, UsartDevice, UsartInterrupt}, 22 | }; 23 | 24 | const BUF_SIZE: usize = 10; 25 | 26 | // Set up static global variables, for sharing state between interrupt contexts and the main loop. 27 | make_globals!((UART, USART1), (READ_BUF, [u8; BUF_SIZE]),); 28 | 29 | make_simple_globals!((READ_I, usize, 0)); 30 | 31 | // If using DMA, we use a static buffer to avoid lifetime problems. 32 | static mut RX_BUF: [u8; BUF_SIZE] = [0; BUF_SIZE]; 33 | const DMA_PERIPH: DmaPeriph = DmaPeriph::Dma1; 34 | const DMA_CH: DmaChannel = DmaChannel::C1; 35 | 36 | #[entry] 37 | fn main() -> ! { 38 | // Set up CPU peripherals 39 | let mut cp = cortex_m::Peripherals::take().unwrap(); 40 | // Set up microcontroller peripherals 41 | let mut dp = pac::Peripherals::take().unwrap(); 42 | 43 | let clock_cfg = Clocks::default(); 44 | clock_cfg.setup().unwrap(); 45 | 46 | // Configure pins for UART, according to the user manual. 47 | let _uart_tx = Pin::new(Port::A, 9, PinMode::Alt(7)); 48 | let _uart_rx = Pin::new(Port::A, 10, PinMode::Alt(7)); 49 | 50 | // Set up the USART1 peripheral. 51 | let mut uart = Usart::new( 52 | dp.USART1, 53 | UsartDevice::One, 54 | 115_200, 55 | UsartConfig::default(), 56 | &clock_cfg, 57 | ); 58 | 59 | uart.enable_interrupt(UsartInterrupt::ReadNotEmpty); 60 | 61 | init_globals!((UART, uart)); 62 | 63 | // Unmask interrupt lines associated with the USART1, and set its priority. 64 | setup_nvic!([(USART1, 1),], cp); 65 | 66 | // Alternative approach using DMA. Note that the specifics of how you implement this 67 | // will depend on the format of data you are reading and writing. Specifically, pay 68 | // attention to how you know when a message starts and ends, if not of a fixed size. 69 | let mut dma = Dma::new(dp.DMA1); 70 | // This DMA MUX step isn't required on F3, F4, and most L4 variants. 71 | dma::mux(DMA_PERIPH, DMA_CH, DmaInput::Usart1Tx); 72 | dma.enable_interrupt(DMA_CH, DmaInterrupt::TransferComplete); 73 | 74 | // Example of how to start a DMA transfer: 75 | unsafe { 76 | uart.read_dma(&mut RX_BUF, DMA_CH, ChannelCfg::default(), DMA_PERIPH); 77 | } 78 | 79 | loop { 80 | low_power::sleep_now(); 81 | } 82 | } 83 | 84 | #[interrupt] 85 | /// Non-blocking USART read interrupt handler; read to a global buffer one byte 86 | /// at a time as we receive them. 87 | fn USART1() { 88 | with(|cs| { 89 | access_global!(UART, uart, cs); 90 | 91 | // Clear the interrupt flag, to prevent this ISR from repeatedly firing 92 | uart.clear_interrupt(UsartInterrupt::ReadNotEmpty); 93 | 94 | let mut buf = READ_BUF.borrow(cs).borrow_mut(); 95 | 96 | let i = READ_I.borrow(cs); 97 | let i_val = i.get(); 98 | if i_val == BUF_SIZE { 99 | // todo: signal end of read. 100 | } 101 | 102 | buf[i_val] = uart.read_one(); 103 | i.set(i_val + 1); 104 | }); 105 | } 106 | 107 | #[interrupt] 108 | /// The transfer complete interrupt for our alternative, DMA-based approach. Note that even when 109 | /// using DMA, you may want to use a UART interrupt to end the transfer. 110 | fn DMA1_CH1() { 111 | with(|cs| { 112 | access_global!(UART, uart, cs); 113 | 114 | // Clear the interrupt flag, to prevent this ISR from repeatedly firing 115 | dma::clear_interrupt(DMA_PERIPH, DMA_CH, DmaInterrupt::TransferComplete); 116 | 117 | // (Handle the data, which is now populated in `RX_BUF`.) 118 | 119 | // You may need to explicitly stop the transfer, to prevent future transfers from failing. 120 | // This is true for writes; not sure if required for reads as well. 121 | dma::stop(DMA_PERIPH, DMA_CH); 122 | 123 | // Start a new transfer, if appropriate for the protocol you're using. 124 | unsafe { 125 | uart.read_dma(&mut RX_BUF, DMA_CH, ChannelCfg::default(), DMA_PERIPH); 126 | } 127 | }); 128 | } 129 | 130 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 131 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 132 | #[defmt::panic_handler] 133 | fn panic() -> ! { 134 | cortex_m::asm::udf() 135 | } 136 | -------------------------------------------------------------------------------- /examples/timer.rs: -------------------------------------------------------------------------------- 1 | //! This module includes an overview of timer 2 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 3 | 4 | #![no_main] 5 | #![no_std] 6 | 7 | use cortex_m_rt::entry; 8 | use critical_section::with; 9 | use hal::{ 10 | clocks::Clocks, 11 | gpio::{Edge, Pin, PinMode, Port}, 12 | low_power, pac, 13 | prelude::*, 14 | setup_nvic, 15 | timer::{ 16 | Alignment, BasicTimer, CaptureCompare, CountDir, InputSlaveMode, InputTrigger, 17 | MasterModeSelection, OutputCompare, TimChannel, Timer, TimerConfig, TimerInterrupt, 18 | }, 19 | }; 20 | 21 | #[entry] 22 | fn main() -> ! { 23 | let mut cp = cortex_m::Peripherals::take().unwrap(); 24 | // Set up microcontroller peripherals 25 | let mut dp = pac::Peripherals::take().unwrap(); 26 | 27 | let clock_cfg = Clocks::default(); 28 | 29 | clock_cfg.setup().unwrap(); 30 | 31 | // Set up a PWM pin 32 | let _pwm_pin = Pin::new(Port::A, 0, PinMode::Alt(1)); 33 | 34 | // Set up a PWM timer that will output to PA0, run at 2400Hz in edge-aligned mode, 35 | // count up, with a 50% duty cycle. 36 | let mut pwm_timer = Timer::new_tim2( 37 | dp.TIM2, 38 | 2_400., 39 | TimerConfig { 40 | auto_reload_preload: true, 41 | // Setting auto reload preload allow changing frequency (period) while the timer is running. 42 | ..Default::default() 43 | }, 44 | &clock_cfg, 45 | ); 46 | 47 | // Example syntax to set up PWM output on channel1, with a 50% duty cycle. 48 | pwm_timer.enable_pwm_output(TimChannel::C1, OutputCompare::Pwm1, 0.5); 49 | 50 | // Example syntax for enabling input capture. Eg, for PWM input 51 | pwm_timer.set_input_capture( 52 | TimChannel::C2, 53 | CaptureCompare::InputTi1, 54 | InputTrigger::Internal0, 55 | InputSlaveMode::Disabled, 56 | true, 57 | false, 58 | ); 59 | 60 | pwm_timer.enable(); 61 | 62 | // Change the duty cycle. The argument is the auto-reload value (ARR). 63 | pwm_timer.set_duty(TimChannel::C1, 100); 64 | 65 | // Exampler of more settings 66 | let timer_config = TimerConfig { 67 | one_pulse_mode: true, 68 | alignment: Alignment::Edge, 69 | direction: CountDir::Down, 70 | ..Default::default() 71 | }; 72 | 73 | let period = 2.; // seconds. 74 | let mut countdown_timer = Timer::new_tim3(dp.TIM3, countdown_period, timer_config, &clock_cfg); 75 | countdown_timer.enable_interrupt(TimerInterrupt::Update); // Enable update event interrupts. 76 | 77 | countdown_timer.enable(); // Start the counter. 78 | 79 | countdown_timer.disable(); // Stop the counter. 80 | 81 | countdown_timer.reset_count(); // Reset the count to 0. 82 | 83 | // Change the frequency to 1Khz. 84 | pwm_timer.set_freq(1_000.); 85 | 86 | // Or set PSC and ARR manually, eg to set period (freq), without running the calculations 87 | // used in `set_freq`. 88 | pwm_timer.set_auto_reload(100); 89 | pwm_timer.set_prescaler(100); 90 | 91 | let seconds_elapsed = countdown_timer.read_count() / countdown_timer.get_max_duty() * period; 92 | println!("Time elapsed since timer start: {}", seconds_elapsed); 93 | 94 | // Set up a basic timer, eg for DAC triggering 95 | let mut dac_timer = BasicTimer::new( 96 | dp.TIM6, 97 | clock_cfg.sai1_speed() as f32 / (64. * 8.), 98 | &clock_cfg, 99 | ); 100 | 101 | // The update event is selected as a trigger output (TRGO). For instance a 102 | // master timer can then be used as a prescaler for a slave timer. 103 | dac_timer.set_mastermode(MasterModeSelection::Update); 104 | 105 | // We can use burst DMA to set the contents of any timer register repeatedly on timeout, based 106 | // on a buffer. For example, you can uses this to dynamically alter a PWM duty cycle, creating 107 | // arbitrary waveforms. 108 | 109 | let mut dma = Dma::new(dp.DMA1); 110 | dma::mux(DmaPeriph::Dma1, DmaChannel::C1, DmaInput::Tim3Up); 111 | timer.enable_interrupt(TimerInterrupt::UpdateDma); 112 | 113 | timer.rotors.write_dma_burst( 114 | &PAYLOAD, 115 | /// Calculate the offset by taking the Adddress Offset for the associated CCR channel in the 116 | /// RM register table, and dividing by 4. 117 | 13, 118 | 4, // Burst le. Eg if updating 4 channels. 119 | DmaChannel::C1, 120 | Default::default(), 121 | true, // true if a 32-bit timer. 122 | DmaPeriph::Dma1, 123 | ); 124 | 125 | // todo: realistic examples of various uses of timers etc. 126 | 127 | // Unmask the interrupt line, and set its priority. 128 | setup_nvic!([(TIM3, 1)], cp); 129 | 130 | loop { 131 | low_power::sleep_now(); 132 | } 133 | } 134 | 135 | #[interrupt] 136 | /// Timer interrupt handler; runs when the countdown period expires. 137 | fn TIM3() { 138 | timer::clear_update_interrupt(3); 139 | 140 | // Do something. 141 | } 142 | 143 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 144 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 145 | #[defmt::panic_handler] 146 | fn panic() -> ! { 147 | cortex_m::asm::udf() 148 | } 149 | -------------------------------------------------------------------------------- /examples/i2c.rs: -------------------------------------------------------------------------------- 1 | //! This module includes an overview of I2C features available. 2 | //! It demonstrates how to configure and take readings from a TI ADS1115 3 | //! offboard ADC. 4 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 5 | 6 | #![no_main] 7 | #![no_std] 8 | 9 | use cortex_m_rt::entry; 10 | use critical_section::with; 11 | use defmt::println; 12 | use hal::{ 13 | clocks::Clocks, 14 | dma::{self, Dma, DmaChannel, DmaInterrupt, DmaPeriph, DmaWriteBuf}, 15 | gpio::{Pin, PinMode, Port}, 16 | i2c::{I2c, I2cConfig, I2cSpeed, NoiseFilter}, 17 | init_globals, low_power, 18 | pac::{self, I2C1}, 19 | setup_nvic, 20 | }; 21 | 22 | static WRITE_BUF: [u8; 2] = [0, 0]; 23 | 24 | static mut READ_BUF: [u8; 8] = [0; 8]; 25 | 26 | const ADDR: u8 = 0x48; 27 | 28 | const DMA_PERIPH: DmaPeriph = DmaPeriph::Dma1; 29 | const TX_CH: DmaChannel = DmaChannel::C6; 30 | const RX_CH: DmaChannel = DmaChannel::C7; 31 | 32 | make_globals!((I2C, I2c),); 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | // Set up CPU peripherals 37 | let mut cp = cortex_m::Peripherals::take().unwrap(); 38 | // Set up microcontroller peripherals 39 | let mut dp = pac::Peripherals::take().unwrap(); 40 | 41 | let clock_cfg = Clocks::default(); 42 | 43 | clock_cfg.setup().unwrap(); 44 | 45 | // Configure pins for I2c. 46 | let mut scl = Pin::new(Port::B, 6, PinMode::Alt(4)); 47 | scl.output_type(OutputType::OpenDrain); 48 | 49 | let mut sda = Pin::new(Port::B, 7, PinMode::Alt(4)); 50 | sda.output_type(OutputType::OpenDrain); 51 | 52 | // Set up an I2C peripheral, running at 100Khz. 53 | let i2c = I2c::new(dp.I2C1, Default::default(), &clock_cfg); 54 | 55 | let i2c_cfg = I2cConfig { 56 | speed: I2cSpeed::Fast400K, // Set to Fast mode, at 400Khz. 57 | // Set a digital noise filter instead of the default analog one. 58 | noise_filter: NoiseFilter::Digitial(5), 59 | ..Default::default() 60 | }; 61 | 62 | // Or, customize the config, including setting different preset speeds: 63 | let i2c = I2c::new(dp.I2C1, i2c_cfg, &clock_cfg); 64 | 65 | // todo: Show how to set up SMBUS. 66 | 67 | // Configure settings for the ADS1115 ADC: 68 | // This config is the 16-bit register contents to set up the ADC in one-shot mode 69 | // with certain settings, and initiate a conversion. For I2C communications, we 70 | // use u8 words. Since this ADC uses 16-bit registers, we split into bytes. 71 | let cfg = [0b1000_0101, 0b1000_0000]; 72 | let cfg_reg = 0x1; 73 | let conversion_reg = 0x0; 74 | 75 | // Set up DMA, for nonblocking (generally faster) conversion transfers: 76 | let mut dma = Dma::new(&mut dp.DMA1); 77 | 78 | // Associate DMA channels with I2C1: One for transmit; one for receive. 79 | // Note that mux is not used on F3, F4, and most L4s: DMA channels are hard-coded 80 | // to peripherals on those platforms. 81 | dma::mux(DMA_PERIPH, TX_CH, DmaInput::I2c1Tx); 82 | dma::mux(DMA_PERIPH, RX_CH, DmaInput::I2c1Rx); 83 | 84 | // Write to DMA, requesting readings, 85 | // todo: put this in a loop, timer interrupt etc to request readings when required. 86 | unsafe { 87 | i2c.write_dma( 88 | ADDR, 89 | &WRITE_BUF, 90 | false, 91 | TX_CH, 92 | Default::default(), 93 | DMA_PERIPH, 94 | ); 95 | } 96 | 97 | // Alternatively, use the blocking, non-DMA I2C API`; see the interrupt handlers below. 98 | let mut read_buf = [0, 0]; 99 | 100 | // Write the config register address, then the 2 bytes of the value we're writing. 101 | i2c.write(ADDR, &[cfg_reg, cfg[0], cfg[1]]).ok(); 102 | // Now request a reading by passing the conversion reg, and a buffer to write 103 | // the results to. 104 | i2c.write_read(ADDR, &[conversion_reg], &mut read_buf).ok(); 105 | let reading = i16::from_be_bytes([read_buf[0], read_buf[1]]); 106 | 107 | init_globals!((I2C, i2c),); 108 | 109 | // Unmask the interrupt lines. See the `DMA_CH6` and `DMA_CH7` interrupt handlers below. 110 | setup_nvic!([(DMA1_CH6, 3), (DMA2_CH7, 3),], cp); 111 | 112 | loop { 113 | low_power::sleep_now(); 114 | } 115 | } 116 | 117 | #[interrupt] 118 | /// This interrupt fires when a DMA transmission is complete. Read the results. 119 | fn DMA1_CH6() { 120 | dma::clear_interrupt(DMA_PERIPH, TX_CH, DmaInterrupt::TransferComplete); 121 | 122 | dma::stop(DMA_PERIPH, TX_CH); 123 | 124 | with(|cs| { 125 | access_global!(I2C, i2c, cs); 126 | unsafe { 127 | i2c.read_dma(ADDR, &mut READ_BUF, RX_CH, Default::default(), DMA_PERIPH); 128 | } 129 | }); 130 | 131 | println!("I2C write complete; reading."); 132 | } 133 | 134 | #[interrupt] 135 | /// This interrupt fires when a DMA read is complete. Handle readings. 136 | fn DMA1_CH7() { 137 | dma::clear_interrupt(DMA_PERIPH, RX_CH, DmaInterrupt::TransferComplete); 138 | 139 | dma::stop(DMA_PERIPH, RX_CH); 140 | 141 | let buf = unsafe { &READ_BUF }; 142 | 143 | println!("I2C data is available: {:?}", buf); 144 | 145 | // The readings (etc) are ready; parse `buf` as required. 146 | } 147 | 148 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 149 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 150 | #[defmt::panic_handler] 151 | fn panic() -> ! { 152 | cortex_m::asm::udf() 153 | } 154 | -------------------------------------------------------------------------------- /examples/spi.rs: -------------------------------------------------------------------------------- 1 | //! This module includes an overview of I2C features available. 2 | //! It demonstrates how to configure and take readings from a TI ADS1115 3 | //! offboard ADC. 4 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 5 | 6 | #![no_main] 7 | #![no_std] 8 | 9 | use cortex_m_rt::entry; 10 | use critical_section::with; 11 | use hal::{ 12 | clocks::Clocks, 13 | dma::{self, Dma, DmaChannel, DmaInput, DmaInterrupt, DmaPeriph, DmaWriteBuf}, 14 | gpio::{self, Pin, PinMode, Port}, 15 | low_power, 16 | pac::{self, interrupt}, 17 | prelude::*, 18 | spi::{self, BaudRate, Spi, SpiConfig, SpiMode}, 19 | }; 20 | 21 | // Byte 0 is for the address we pass in the `write` transfer; relevant data is in the rest of 22 | // the values. 23 | static mut SPI_READ_BUF: [u8; 4] = [0; 4]; 24 | static mut SPI_WRITE_BUF: [u8; 4] = [0x69, 0, 0, 0]; 25 | 26 | make_globals!((SPI, Spi)); 27 | 28 | #[entry] 29 | fn main() -> ! { 30 | // Set up CPU peripherals 31 | let mut cp = cortex_m::Peripherals::take().unwrap(); 32 | // Set up microcontroller peripherals 33 | let mut dp = pac::Peripherals::take().unwrap(); 34 | 35 | let clock_cfg = Clocks::default(); 36 | 37 | clock_cfg.setup().unwrap(); 38 | 39 | // Configure pins for Spi 40 | let _sck = Pin::new(Port::A, 5, PinMode::Alt(5)); 41 | let _miso = Pin::new(Port::A, 6, PinMode::Alt(5)); 42 | let _mosi = Pin::new(Port::A, 7, PinMode::Alt(5)); 43 | 44 | let mut cs = Pin::new(Port::A, 1, PinMode::Output); 45 | 46 | let spi_cfg = SpiConfig { 47 | mode: SpiMode::mode1(), 48 | // `SpiConfig::default` is mode 0, full duplex, with software CS. 49 | ..Default::default() 50 | }; 51 | 52 | // Alternatively, we can configure Mode py polarity and phase: 53 | // mode: SpiMode { 54 | // polarity: SpiPolarity::IdleLow, 55 | // phase: SpiPhase::CaptureOnFirstTransition, 56 | // } 57 | 58 | // Set up an SPI peripheral, running at 4Mhz, in SPI mode 0. 59 | let mut spi = Spi::new( 60 | dp.SPI1, 61 | spi_cfg, 62 | BaudRate::Div32, // Eg 80Mhz apb clock / 32 = 2.5Mhz SPI clock. 63 | ); 64 | 65 | // Set up DMA, for nonblocking (generally faster) conversion transfers: 66 | let mut dma = Dma::new(&mut dp.DMA1, &dp.RCC); 67 | 68 | // Associate a pair of DMA channels with SPI1: One for transmit; one for receive. 69 | // Note that mux is not used on F3, F4, and most L4s: DMA channels are hard-coded 70 | // to peripherals on those platforms. 71 | dma::mux(DmaPeriph::Dma2, DmaChannel::C1, DmaInput::Spi1Tx); 72 | dma::mux(DmaPeriph::Dma2, DmaChannel::C2, DmaInput::Spi1Rx); 73 | 74 | cs.set_low(); 75 | 76 | unsafe { 77 | // Write to SPI, using DMA. 78 | // spi.write_dma(&write_buf, DmaChannel::C1, Default::default(), DmaPeriph::Dma2); 79 | 80 | // Read (transfer) from SPI, using DMA. 81 | spi.transfer_dma( 82 | // Write buffer, starting with the registers we'd like to access, and 0-padded to 83 | // read 3 bytes. 84 | &SPI_WRITE_BUF, 85 | &mut SPI_READ_BUF, // Read buf, where the data will go 86 | DmaChannel::C1, // Write channel 87 | DmaChannel::C2, // Read channel 88 | Default::default(), // Write channel config 89 | Default::default(), // Read channel config 90 | DmaPeriph::Dma2, 91 | ); 92 | } 93 | 94 | // Alternatively, use the blocking, non-DMA SPI API` (Also supports `embedded-hal` traits): 95 | 96 | // We read 3 bytes from the `0x9f` register. 97 | let mut write_buf = [0x80, 100]; 98 | let mut read_buf = [0x9f, 0, 0, 0]; 99 | spi.write(&write_buf).ok(); 100 | spi.transfer(&mut read_buf).ok(); 101 | defmt::println!("Data: {}", read_buf); 102 | 103 | // Assign peripheral structs as global, so we can access them in interrupts. 104 | init_globals!((DMA, dma), (SPI, spi)); 105 | 106 | // Unmask the interrupt line for DMA read complete. See the `DMA_CH2` interrupt handlers below, 107 | // where we set CS high, terminal the DMA read, and display the data read. 108 | setup_nvic!([(DMA1_CH2, 1)], cp); 109 | 110 | // Alternatively, we can take readings without DMA. This provides a simpler, memory-safe API, 111 | // and is compatible with the `embedded_hal::blocking::i2c traits. 112 | 113 | loop { 114 | low_power::sleep_now(); 115 | } 116 | } 117 | 118 | #[interrupt] 119 | /// This interrupt fires when a DMA read is complete 120 | fn DMA1_CH2() { 121 | dma::clear_interrupt( 122 | DmaPeriph::Dma2, 123 | DmaChannel::C2, 124 | DmaInterrupt::TransferComplete, 125 | ); 126 | with(|cs| { 127 | defmt::println!("SPI DMA read complete"); 128 | access_global!(SPI, spi, cs); 129 | spi.stop_dma(DmaChannel::C1, Some(DmaChannel::C2), DmaPeriph::Dma2); 130 | 131 | // See also this convenience function, which clears the interrupt and stops othe Txfer.: 132 | spi.cleanup_dma(DmaPeriph::Dma2, DmaChannel::C1, Some(DmaChannel::C2)); 133 | 134 | unsafe { 135 | // Ignore byte 0, which is the reg we passed during the write. 136 | println!("Data read: {:?}", SPI_READ_BUF); 137 | } 138 | 139 | // Set CS high. 140 | gpio::set_high(Port::A, 1); 141 | }) 142 | } 143 | 144 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 145 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 146 | #[defmt::panic_handler] 147 | fn panic() -> ! { 148 | cortex_m::asm::udf() 149 | } 150 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Common error definitions. 2 | 3 | #[cfg(not(any(feature = "f", feature = "wb", feature = "wl", feature = "h5")))] 4 | use crate::crc::PolynomialError; 5 | #[cfg(not(any(feature = "l552", feature = "h5", feature = "f4")))] 6 | use crate::dma::DmaError; 7 | #[cfg(not(feature = "h735"))] 8 | use crate::flash::FlashError; 9 | #[cfg(not(any( 10 | feature = "f", 11 | feature = "l4x3", // todo: PAC bug? 12 | feature = "g0", 13 | feature = "g431", 14 | feature = "g441", 15 | feature = "g471", 16 | feature = "g491", 17 | feature = "g4a1", 18 | feature = "wl", 19 | feature = "l5", // todo: PAC errors on some regs. 20 | feature = "h5", 21 | feature = "c0", 22 | )))] 23 | use crate::qspi::QspiError; 24 | #[cfg(not(feature = "f301"))] 25 | use crate::spi::SpiError; 26 | use crate::{clocks::RccError, i2c::I2cError, rtc::RtcError, timer::TimerError, usart::UsartError}; 27 | 28 | macro_rules! impl_from_error { 29 | ($error:ident) => { 30 | impl From<$error> for Error { 31 | fn from(error: $error) -> Self { 32 | Self::$error(error) 33 | } 34 | } 35 | }; 36 | } 37 | 38 | /// Alias for Result. 39 | pub type Result = core::result::Result; 40 | 41 | /// Collection of all errors that can occur. 42 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 43 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 44 | pub enum Error { 45 | /// Occurs when an expected change of a register does happen in time. 46 | /// 47 | /// This is returned when a bounded loop exceeds its alotted iteration count. 48 | RegisterUnchanged, 49 | #[cfg(not(any(feature = "l552", feature = "h5", feature = "f4")))] 50 | /// Direct Memory Access (DMA) error 51 | DmaError(DmaError), 52 | I2cError(I2cError), 53 | UsartError(UsartError), 54 | TimerError(TimerError), 55 | /// 56 | #[cfg(not(feature = "h735"))] 57 | FlashError(FlashError), 58 | #[cfg(not(any(feature = "f", feature = "wb", feature = "wl", feature = "h5")))] 59 | /// CRC 60 | PolynomialError(PolynomialError), 61 | #[cfg(not(feature = "f301"))] 62 | /// SPI errors. 63 | SpiError(SpiError), 64 | /// Clock errors. 65 | RtcError(RtcError), 66 | RccError(RccError), 67 | #[cfg(not(any( 68 | feature = "f", 69 | feature = "l4x3", // todo: PAC bug? 70 | feature = "g0", 71 | feature = "g431", 72 | feature = "g441", 73 | feature = "g471", 74 | feature = "g491", 75 | feature = "g4a1", 76 | feature = "wl", 77 | feature = "l5", // todo: PAC errors on some regs. 78 | feature = "h5", 79 | feature = "c0", 80 | )))] 81 | QspiError(QspiError), 82 | } 83 | 84 | #[cfg(not(any(feature = "l552", feature = "h5", feature = "f4")))] 85 | impl_from_error!(DmaError); 86 | impl_from_error!(I2cError); 87 | impl_from_error!(UsartError); 88 | impl_from_error!(TimerError); 89 | #[cfg(not(feature = "h735"))] 90 | impl_from_error!(FlashError); 91 | #[cfg(not(any(feature = "f", feature = "wb", feature = "wl", feature = "h5")))] 92 | impl_from_error!(PolynomialError); 93 | #[cfg(not(feature = "f301"))] 94 | impl_from_error!(SpiError); 95 | impl_from_error!(RtcError); 96 | impl_from_error!(RccError); 97 | #[cfg(not(any( 98 | feature = "f", 99 | feature = "l4x3", // todo: PAC bug? 100 | feature = "g0", 101 | feature = "g431", 102 | feature = "g441", 103 | feature = "g471", 104 | feature = "g491", 105 | feature = "g4a1", 106 | feature = "wl", 107 | feature = "l5", // todo: PAC errors on some regs. 108 | feature = "h5", 109 | feature = "c0", 110 | )))] 111 | impl_from_error!(QspiError); 112 | 113 | #[cfg(feature = "embedded_hal")] 114 | mod embedded_io_impl { 115 | use embedded_hal::i2c::{Error as I2cEhError, ErrorKind as I2cErrorKind, NoAcknowledgeSource}; 116 | use embedded_io::{Error as IoError, ErrorKind as IoErrorKind}; 117 | 118 | use super::{Error, I2cError, UsartError}; 119 | 120 | // Other, 121 | // NotFound, 122 | // PermissionDenied, 123 | // ConnectionRefused, 124 | // ConnectionReset, 125 | // ConnectionAborted, 126 | // NotConnected, 127 | // AddrInUse, 128 | // AddrNotAvailable, 129 | // BrokenPipe, 130 | // AlreadyExists, 131 | // InvalidInput, 132 | // InvalidData, 133 | // TimedOut, 134 | // Interrupted, 135 | // Unsupported, 136 | // OutOfMemory, 137 | // WriteZero, 138 | 139 | impl I2cEhError for Error { 140 | fn kind(&self) -> I2cErrorKind { 141 | match self { 142 | Error::I2cError(i) => match i { 143 | I2cError::Bus => I2cErrorKind::Bus, 144 | I2cError::Arbitration => I2cErrorKind::ArbitrationLoss, 145 | I2cError::Nack => I2cErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), 146 | I2cError::Overrun => I2cErrorKind::Overrun, 147 | _ => I2cErrorKind::Other, 148 | // I2cError::Other => I2cErrorKind::Other, 149 | }, 150 | _ => I2cErrorKind::Other, 151 | } 152 | } 153 | } 154 | 155 | impl IoError for Error { 156 | fn kind(&self) -> IoErrorKind { 157 | match self { 158 | Error::RegisterUnchanged => IoErrorKind::TimedOut, 159 | Error::UsartError(u) => match u { 160 | UsartError::Framing => IoErrorKind::Other, 161 | UsartError::Noise => IoErrorKind::Other, 162 | UsartError::Overrun => IoErrorKind::OutOfMemory, 163 | UsartError::Parity => IoErrorKind::InvalidData, 164 | }, 165 | _ => IoErrorKind::Other, 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/can.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrates use of the FD-CAN peripheral, using the `can-fd` crate. 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use cortex_m::{delay::Delay, interrupt}; 7 | use cortex_m_rt::entry; 8 | use critical_section::with; 9 | use fdcan::{ 10 | FdCan, NormalOperationMode, 11 | frame::{FrameFormat, TxFrameHeader}, 12 | id::{ExtendedId, Id}, 13 | interrupt::{Interrupt, InterruptLine}, 14 | }; 15 | use hal::{ 16 | can::{self, Can}, 17 | clocks::Clocks, 18 | gpio::{self, Edge, OutputSpeed, Pin, PinMode, PinState, Port}, 19 | pac, 20 | }; 21 | 22 | // Code shortener to isolate typestate syntax. 23 | type Can_ = FdCan; 24 | 25 | /// An example function to set up the pins that don't need to be interacted with directly later. 26 | /// For example, ones used with buses (eg I2C, SPI, UART), USB, ADC, and DAC pins. 27 | /// This may also include input pins that trigger interrupts, and aren't polled. 28 | pub fn setup_pins() { 29 | let mut can_rx = Pin::new(Port::A, 11, PinMode::Alt(9)); 30 | let mut can_tx = Pin::new(Port::A, 12, PinMode::Alt(9)); 31 | 32 | can_tx.output_speed(OutputSpeed::VeryHigh); 33 | can_rx.output_speed(OutputSpeed::VeryHigh); 34 | } 35 | 36 | pub fn setup_can(can_pac: pac::FDCAN1) -> Can_ { 37 | // todo: CAN clock cfg. Can be on PCLK1 (170Mhz), orPLLQ. (Should be able to 38 | // todo get a custom speed there) 39 | let mut can = FdCan::new(Can::new(can_pac)).into_config_mode(); 40 | 41 | // Kernel Clock 170MHz, Bit rate: 1MBit/s, Sample Point 87.5% 42 | // An example of where to get values: http://www.bittiming.can-wiki.info/ 43 | // These depicated are for 1Mbit/s, using a 170Mhz clock. 44 | 45 | // Some values from https://dronecan.github.io/Specification/8._Hardware_design_recommendations/ 46 | let nominal_bit_timing = config::NominalBitTiming { 47 | prescaler: NonZeroU16::new(10).unwrap(), 48 | seg1: NonZeroU8::new(14).unwrap(), 49 | seg2: NonZeroU8::new(2).unwrap(), 50 | sync_jump_width: NonZeroU8::new(1).unwrap(), 51 | }; 52 | 53 | let data_bit_timing = config::DataBitTiming { 54 | prescaler: NonZeroU8::new(10).unwrap(), 55 | seg1: NonZeroU8::new(14).unwrap(), 56 | seg2: NonZeroU8::new(2).unwrap(), 57 | sync_jump_width: NonZeroU8::new(1).unwrap(), 58 | transceiver_delay_compensation: true, 59 | }; 60 | 61 | can.set_protocol_exception_handling(false); 62 | can.set_nominal_bit_timing(nominal_bit_timing); 63 | can.set_data_bit_timing(data_bit_timing); 64 | 65 | can.set_standard_filter( 66 | StandardFilterSlot::_0, 67 | StandardFilter::accept_all_into_fifo0(), 68 | ); 69 | 70 | can.set_extended_filter( 71 | ExtendedFilterSlot::_0, 72 | ExtendedFilter::accept_all_into_fifo0(), 73 | ); 74 | 75 | let can_cfg = can 76 | .get_config() 77 | .set_frame_transmit(config::FrameTransmissionConfig::AllowFdCanAndBRS); 78 | 79 | can.apply_config(can_cfg); 80 | 81 | can.enable_interrupt(Interrupt::RxFifo0NewMsg); 82 | can.enable_interrupt_line(InterruptLine::_0, true); 83 | 84 | can.into_normal() 85 | } 86 | 87 | #[entry] 88 | fn main() -> ! { 89 | // Set up CPU peripherals 90 | let mut cp = cortex_m::Peripherals::take().unwrap(); 91 | // Set up microcontroller peripherals 92 | let mut dp = pac::Peripherals::take().unwrap(); 93 | 94 | let clock_cfg = Clocks::default(); 95 | // Note: Make sure your clocks are set up for CAN. On G4 the default setup should work. 96 | // On H7, you may need to explicitly enable PLLQ1l, which is the default can clock source. 97 | // You can change the can clock source using the config as well. For example: 98 | // 99 | // let clock_cfg = Clocks { 100 | // pll_src: PllSrc::Hse(16_000_000), 101 | // pll1: PllCfg { 102 | // divm: 8, 103 | // pllq_en: true, // PLLQ for CAN clock. Its default div of 8 is fine. 104 | // ..Default::default() 105 | // } 106 | // }; 107 | 108 | if clock_cfg.setup().is_err() { 109 | defmt::error!("Unable to configure clocks due to a speed error.") 110 | }; 111 | 112 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 113 | 114 | // Call a function we've made to help organize our pin setup code. 115 | setup_pins(); 116 | 117 | let mut can = setup_can(dp.FDCAN1); 118 | 119 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 120 | 121 | println!("Starting send loop; periodically send a message."); 122 | 123 | let tx_buf = [0, 1, 2, 3]; 124 | let mut rx_buf = [0; 4]; 125 | 126 | loop { 127 | let id = Id::Extended(ExtendedId::new(1).unwrap()); 128 | 129 | let frame_header = TxFrameHeader { 130 | len: tx_buf.len(), 131 | frame_format: FrameFormat::Fdcan, 132 | id, 133 | bit_rate_switching: true, 134 | marker: None, 135 | }; 136 | 137 | if let Err(e) = can.transmit(frame_header, &tx_buf) { 138 | println!("Error node"); 139 | } else { 140 | println!("Success node"); 141 | } 142 | 143 | delay.delay_ms(500); 144 | 145 | // An example of receive syntax: 146 | let rx_result = can.receive0(&mut rx_buf); 147 | 148 | match rx_result { 149 | Ok(r) => { 150 | println!("Ok"); 151 | println!("Rx buf: {:?}", rx_buf); 152 | } 153 | Err(e) => { 154 | // println!("error"); 155 | } 156 | } 157 | } 158 | } 159 | 160 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 161 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 162 | #[defmt::panic_handler] 163 | fn panic() -> ! { 164 | cortex_m::asm::udf() 165 | } 166 | -------------------------------------------------------------------------------- /examples/usb_serial.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates serial communication with a PC. 2 | 3 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 4 | 5 | #![no_main] 6 | #![no_std] 7 | 8 | use cortex_m; 9 | use cortex_m_rt::entry; 10 | use critical_section::{CriticalSection, with}; 11 | use defmt::println; 12 | use defmt_rtt as _; 13 | use hal::{ 14 | clocks::{self, Clk48Src, Clocks, CrsSyncSrc, enable_crs}, 15 | gpio::{Pin, PinMode, Port}, 16 | pac, 17 | prelude::*, 18 | usb::{Peripheral, UsbBus, UsbBusType}, 19 | }; 20 | use panic_probe as _; 21 | use usb_device::{bus::UsbBusAllocator, prelude::*}; 22 | use usbd_serial::{SerialPort, USB_CLASS_CDC}; 23 | 24 | static mut USB_BUS: Option> = None; 25 | 26 | // See note below about if using custom buffer sizes. 27 | // pub type UsbSerial<'a> = SerialPort<'a, UsbBusType, &'a mut [u8], &'a mut [u8]>; 28 | 29 | make_globals!( 30 | (USB_DEV, UsbDevice<'static, UsbBusType>), 31 | (USB_SERIAL, SerialPort<'static, UsbBusType>), 32 | // Or, if you use custom buffer sizes: 33 | // (USB_SERIAL, UsbSerial<'static>), 34 | ); 35 | 36 | fn init() { 37 | // Set up CPU peripherals 38 | let mut cp = cortex_m::Peripherals::take().unwrap(); 39 | // Set up microcontroller peripherals 40 | let mut dp = pac::Peripherals::take().unwrap(); 41 | 42 | let mut clock_cfg = Clocks { 43 | // Enable the HSI48 oscillator, so we don't need an external oscillator, and 44 | // aren't restricted in our PLL config. 45 | hsi48_on: true, 46 | clk48_src: Clk48Src::Hsi48, 47 | ..Default::default() 48 | }; 49 | 50 | clock_cfg.setup().unwrap(); 51 | 52 | // Enable the Clock Recovery System, which improves HSI48 accuracy. 53 | clock_cfg::enable_crs(CrsSyncSrc::Usb); 54 | 55 | // Enable `pwren`. Note that this is also set up by the `rtc` initialization, so this 56 | // step isn't required if you have the RTC set up. Only required on some configurations, 57 | // , e.g. L4, L5, and G0. 58 | // dp.RCC.apb1enr1().modify(|_, w| w.pwren().bit(true)); 59 | 60 | // Enable USB power, on applicable MCUs, e.g. L4, L5, and G0. 61 | // usb::enable_usb_pwr(&mut dp.PWR, &mut dp.RCC); 62 | 63 | // Set up USB pins. Note: This only applies to some MCUs; others don't require this, 64 | // nor have the appropriate alt functions. 65 | // let _usb_dm = gpioa.new_pin(11, PinMode::Alt(14)); 66 | // let _usb_dp = gpioa.new_pin(12, PinMode::Alt(14)); 67 | 68 | let usb = Peripheral { usb: dp.USB }; 69 | let usb_bus = UsbBus::new(usb); 70 | 71 | let usb_serial = SerialPort::new(usb_bus); 72 | 73 | // Or, if specifying manual buffers, e.g. larger than the default: 74 | let usb_serial = SerialPort::new_with_store( 75 | unsafe { USB_BUS.as_ref().unwrap() }, 76 | unsafe { &mut USB_TX_STORE[..] }, 77 | unsafe { &mut USB_RX_STORE[..] }, 78 | ); 79 | 80 | let usb_dev = UsbDeviceBuilder::new( 81 | unsafe { USB_BUS.as_ref().unwrap() }, 82 | UsbVidPid(0x16c0, 0x27dd), 83 | ) 84 | .strings(&[StringDescriptors::default() 85 | .manufacturer("A Company") 86 | .product("Serial port") 87 | // We use `serial_number` to identify the device to the PC. If it's too long, 88 | // we get permissions errors on the PC. 89 | .serial_number("SN")]) 90 | .unwrap() 91 | .device_class(usbd_serial::USB_CLASS_CDC) 92 | .build(); 93 | 94 | init_globals!((USB_DEVICE, usb_device), (USB_SERIAL, usb_serial)); 95 | 96 | setup_nvic!([(USB_FS, 1),], cp); 97 | } 98 | 99 | /// Handle incoming data from the USB port. 100 | pub fn handle_rx( 101 | usb_serial: &mut UsbSerial<'static>, 102 | usb_dev: &mut UsbDevice<'static, UsbBusType>, 103 | rx_buf: &[u8], 104 | cs: CriticalSection, 105 | ) { 106 | // We have access to the RX buffer in this function. 107 | println!("Received a message over USB: {:?}", rx_buf); 108 | 109 | // Example sending a message: 110 | if rx_buf[0] == 10 { 111 | // Populate A/R. 112 | let mut tx_buf = [0; 10]; 113 | 114 | let msg_len = 10; 115 | 116 | let mut offset = 0; 117 | while offset < msg_len { 118 | match usb_serial.write(&tx_buf[offset..msg_len]) { 119 | Ok(0) | Err(UsbError::WouldBlock) => { 120 | usb_dev.poll(&mut [usb_serial]); 121 | } 122 | Ok(written) => offset += written, 123 | Err(e) => { 124 | defmt::warn!("USB write error: {:?}", e); 125 | break; 126 | } 127 | } 128 | } 129 | 130 | while usb_serial.flush().err() == Some(UsbError::WouldBlock) { 131 | usb_dev.poll(&mut [usb_serial]); 132 | } 133 | } 134 | } 135 | 136 | #[entry] 137 | fn main() -> ! { 138 | init(); 139 | 140 | loop { 141 | asm::nop(); 142 | } 143 | } 144 | 145 | #[interrupt] 146 | /// Interrupt handler for USB (serial) 147 | /// Note that the name of this handler depends on the variant assigned in the associated PAC. 148 | /// Other examples include `USB_LP` (G4), and `USB_HS` and `USB_FS` (H7) 149 | fn USB_LP() { 150 | with(|cs| { 151 | access_globals!([(USB_DEV, dev), (USB_SERIAL, serial),], cs); 152 | 153 | if !dev.poll(&mut [serial]) { 154 | return; 155 | } 156 | 157 | let mut rx_buf = [0; 10]; 158 | 159 | if let Ok(count) = serial.read(&mut rx_buf) { 160 | println!("Bytes read: {}", count); 161 | 162 | handle_rx(serial, dev, &rx_buf, cs); 163 | } 164 | }); 165 | } 166 | 167 | // Same panicking *behavior* as `panic-probe` but doesn't print a panic message 168 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 169 | #[defmt::panic_handler] 170 | fn panic() -> ! { 171 | cortex_m::asm::udf() 172 | } 173 | -------------------------------------------------------------------------------- /examples/interrupts.rs: -------------------------------------------------------------------------------- 1 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use cortex_m_rt::entry; 7 | use critical_section::with; 8 | use stm32_hal::{ 9 | adc::{Adc, AdcChannel, AdcDevice}, 10 | clocks::Clocks, 11 | gpio::{Edge, Pin, PinMode, Port}, 12 | low_power, make_simple_globals, 13 | pac::{self, ADC1, EXTI, interrupt}, 14 | rtc::{Rtc, RtcClockSource, RtcConfig}, 15 | setup_nvic, 16 | timer::{Timer, TimerInterrupt}, 17 | }; 18 | 19 | // We can use this macro for setting up Global `Copy` types, as `Mutexts`. 20 | make_simple_globals!((SENSOR_READING, f32, 335.), (BOUNCING, bool, false)); 21 | 22 | // More complex values go in `RefCell`s. Use an option, since we need to set this up 23 | // before we initialize the peripheral it stores. 24 | make_globals!((ADC, ADC),); 25 | 26 | #[entry] 27 | fn main() -> ! { 28 | // Set up CPU peripherals 29 | let mut cp = cortex_m::Peripherals::take().unwrap(); 30 | // Set up microcontroller peripherals 31 | let mut dp = pac::Peripherals::take().unwrap(); 32 | 33 | let clock_cfg = Clocks::default(); 34 | 35 | clock_cfg.setup().unwrap(); 36 | 37 | // Configure PA0 to trigger a GPIO interrupt. 38 | let mut button = Pin::new(Port::A, 0, PinMode::Input); 39 | button.enable_interrupt(Edge::Falling); 40 | 41 | // Set up and start a timer; set it to fire interrupts every 5 seconds. 42 | let mut timer = Timer::new_tim3(dp.TIM3, 0.2, Default::default(), &clock_cfg); 43 | timer.enable_interrupt(TimerInterrupt::Update); // Enable update event interrupts. 44 | timer.enable(); 45 | 46 | let mut debounce_timer = Timer::new_tim15(dp.TIM15, 5., Default::default(), &clock_cfg); 47 | debounce_timer.enable_interrupt(TimerInterrupt::Update); // Enable update event interrupts. 48 | 49 | // Set up the realtime clock. 50 | let mut rtc = Rtc::new( 51 | dp.RTC, 52 | &mut dp.PWR, 53 | RtcConfig { 54 | clock_source: RtcClockSource::Lse, 55 | ..Default::default() 56 | }, 57 | ); 58 | 59 | // Set the RTC to trigger an interrupt every 30 seconds. 60 | rtc.set_wakeup(&mut dp.EXTI, 30.); 61 | 62 | let mut adc = Adc::new_adc1( 63 | dp.ADC1, 64 | AdcDevice::One, 65 | Default::default(), 66 | clock_cfg.systick(), 67 | ); 68 | adc.enable_interrupt(AdcInterrupt::EndOfConversion); 69 | 70 | // Set up our ADC as a global variable accessible in interrupts, now that it's initialized. 71 | with(|cs| { 72 | ADC.borrow(cs).replace(Some(adc)); 73 | }); 74 | 75 | // Unmask NVIC lines, and set priority. 76 | setup_nvic!([(EXTI0, 0), (TIM3, 1), (TIM15, 2), (RTC_WKUP, 2),], cp); 77 | 78 | // todo: UART interrupts. 79 | 80 | loop { 81 | // Enter a low power mode. 82 | low_power::stop(low_power::StopMode::Two); 83 | 84 | // Turn back on the PLL. 85 | clocks.reselect_input(); 86 | } 87 | } 88 | 89 | #[interrupt] 90 | /// GPIO interrupt 91 | fn EXTI0() { 92 | // Clear the interrupt flag, to prevent continous firing. 93 | gpio::clear_exti_interrupt(0); 94 | 95 | with(|cs| { 96 | let bouncing = BOUNCING.borrow(cs); 97 | if bouncing.get() { 98 | return; 99 | } 100 | unsafe { (*pac::TIM15::ptr()).cr1().modify(|_, w| w.cen().bit(true)) } 101 | bouncing.set(true); 102 | 103 | // Update our global sensor reading. This section dmeonstrates boilerplate 104 | // for Mutexes etc. 105 | // Alternative syntax using a convenience macro: 106 | // `access_global!(ADC, sensor, cs);` 107 | let mut s = ADC.borrow(cs).borrow_mut(); 108 | let sensor = s.as_mut().unwrap(); 109 | let reading = sensor.read(AdcChannel::C1).unwrap(); 110 | SENSOR_READING.borrow(cs).replace(reading); 111 | }); 112 | } 113 | 114 | #[interrupt] 115 | /// RTC wakeup handler 116 | fn RTC_WKUP() { 117 | with(|cs| { 118 | unsafe { 119 | // Reset pending bit for interrupt line; RTC uses EXTI line 20. 120 | gpio::clear_exti_interrupt(20); 121 | 122 | // Clear the wakeup timer flag, after disabling write protections. 123 | (*pac::RTC::ptr()).wpr.write(|w| w.bits(0xCA)); 124 | (*pac::RTC::ptr()).wpr.write(|w| w.bits(0x53)); 125 | (*pac::RTC::ptr()).cr().modify(|_, w| w.wute().clear_bit()); 126 | 127 | (*pac::RTC::ptr()).isr().modify(|_, w| w.wutf().clear_bit()); 128 | 129 | (*pac::RTC::ptr()).cr().modify(|_, w| w.wute().bit(true)); 130 | (*pac::RTC::ptr()).wpr.write(|w| w.bits(0xFF)); 131 | } 132 | 133 | // A cleaner alternative to the above, if you have the RTC set up in a global Mutex: 134 | // unsafe { 135 | // (*pac::EXTI::ptr()).pr1.modify(|_, w| w.pr20().bit(true)); 136 | // } 137 | // access_global!(RTC, rtc, cs); 138 | // rtc.clear_wakeup_flag(); 139 | 140 | // Do something. 141 | }); 142 | } 143 | 144 | #[interrupt] 145 | /// Timer interrupt handler 146 | fn TIM3() { 147 | // Clear the interrupt flag. If you ommit this, it will fire repeatedly. 148 | timer::clear_update_interrupt(3); 149 | 150 | // Do something. 151 | } 152 | 153 | #[interrupt] 154 | /// We use this timer for button debounce. 155 | fn TIM15() { 156 | timer::clear_update_interrupt(15); 157 | with(|cs| { 158 | BOUNCING.borrow(cs).set(false); 159 | 160 | // Disable the timer until next time you press a button. 161 | unsafe { 162 | (*pac::TIM15::ptr()) 163 | .cr1() 164 | .modify(|_, w| w.cen().clear_bit()) 165 | } 166 | }); 167 | } 168 | 169 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 170 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 171 | #[defmt::panic_handler] 172 | fn panic() -> ! { 173 | cortex_m::asm::udf() 174 | } 175 | -------------------------------------------------------------------------------- /examples/waveform_generator.rs: -------------------------------------------------------------------------------- 1 | //! Generates a sine waveform output from the DAC. Demonstrates using of timers to trigger 2 | //! the DAC, and a DMA circular buffer to transfer data from memory to the DAC with minimal 3 | //! CPU intervention. 4 | //! 5 | //! You can view output between ground and PA4 with an oscilloscope, or a pair of headphones or 6 | //! speakers. If using headphones or speakers, make sure you can adjust their volume; the default 7 | //! output volume will be too high. 8 | //! 9 | //! Tested on an STM32H743ZI, via STM32H7 Nucleo dev board. 10 | //! 11 | //! See [STM32 AN3126](https://www.st.com/resource/en/application_note/cd00259245-audio-and-waveform-generation-using-the-dac-in-stm32-products-stmicroelectronics.pdf), 12 | //! "Audio and waveform generation using the DAC in STM32 products." 13 | 14 | #![no_main] 15 | #![no_std] 16 | #![allow(non_snake_case)] 17 | 18 | use core::sync::atomic::{AtomicUsize, Ordering}; 19 | 20 | use cortex_m::{self, delay::Delay}; 21 | use cortex_m_rt::entry; 22 | use critical_section::with; 23 | use defmt_rtt as _; // global logger 24 | use hal::{ 25 | self, 26 | clocks::Clocks, 27 | dac::{Dac, DacBits, DacChannel, Trigger}, 28 | debug_workaround, 29 | dma::{self, Dma, DmaChannel}, 30 | gpio::{OutputType, Pin, PinMode, Port}, 31 | low_power, pac, 32 | timer::{BasicTimer, MasterModeSelection, TimerDevice}, 33 | }; 34 | use panic_probe as _; 35 | 36 | // Length of the lookup table used to generate sin waves etc. 37 | const LUT_LEN: usize = 256; 38 | 39 | // A lookup table for sin(x), over one period, using values from 0 - 4095; ie the full range of 40 | // the STM32 12-bit onboard DAC. Compared to computation, this is faster, at the expense of memory use. 41 | pub static SIN_X: [u16; crate::LUT_LEN] = [ 42 | 2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398, 2447, 2496, 2545, 2594, 2642, 2690, 2737, 2784, 43 | 2831, 2877, 2923, 2968, 3013, 3057, 3100, 3143, 3185, 3226, 3267, 3307, 3346, 3385, 3423, 3459, 44 | 3495, 3530, 3565, 3598, 3630, 3662, 3692, 3722, 3750, 3777, 3804, 3829, 3853, 3876, 3898, 3919, 45 | 3939, 3958, 3975, 3992, 4007, 4021, 4034, 4045, 4056, 4065, 4073, 4080, 4085, 4089, 4093, 4094, 46 | 4095, 4094, 4093, 4089, 4085, 4080, 4073, 4065, 4056, 4045, 4034, 4021, 4007, 3992, 3975, 3958, 47 | 3939, 3919, 3898, 3876, 3853, 3829, 3804, 3777, 3750, 3722, 3692, 3662, 3630, 3598, 3565, 3530, 48 | 3495, 3459, 3423, 3385, 3346, 3307, 3267, 3226, 3185, 3143, 3100, 3057, 3013, 2968, 2923, 2877, 49 | 2831, 2784, 2737, 2690, 2642, 2594, 2545, 2496, 2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098, 50 | 2048, 1997, 1947, 1897, 1847, 1797, 1747, 1697, 1648, 1599, 1550, 1501, 1453, 1405, 1358, 1311, 51 | 1264, 1218, 1172, 1127, 1082, 1038, 995, 952, 910, 869, 828, 788, 749, 710, 672, 636, 600, 565, 52 | 530, 497, 465, 433, 403, 373, 345, 318, 291, 266, 242, 219, 197, 176, 156, 137, 120, 103, 88, 53 | 74, 61, 50, 39, 30, 22, 15, 10, 6, 2, 1, 0, 1, 2, 6, 10, 15, 22, 30, 39, 50, 61, 74, 88, 103, 54 | 120, 137, 156, 176, 197, 219, 242, 266, 291, 318, 345, 373, 403, 433, 465, 497, 530, 565, 600, 55 | 636, 672, 710, 749, 788, 828, 869, 910, 952, 995, 1038, 1082, 1127, 1172, 1218, 1264, 1311, 56 | 1358, 1405, 1453, 1501, 1550, 1599, 1648, 1697, 1747, 1797, 1847, 1897, 1947, 1997, 57 | ]; 58 | 59 | /// Set up the pins that have structs that don't need to be accessed after. 60 | pub fn setup_pins() { 61 | // Set `dac_pin` to analog mode, to prevent parasitic power use. 62 | // DAC1_OUT1 is on PA4. DAC1_OUT2 is on PA5. 63 | let _dac_pin = Pin::new(Port::A, 4, PinMode::Analog); 64 | } 65 | 66 | #[entry] 67 | fn main() -> ! { 68 | // Set up CPU peripherals 69 | let mut cp = cortex_m::Peripherals::take().unwrap(); 70 | // Set up microcontroller peripherals 71 | let mut dp = pac::Peripherals::take().unwrap(); 72 | 73 | // Set up clocks 74 | let clock_cfg = Clocks::default(); 75 | clock_cfg 76 | .setup(&mut dp.RCC, &mut dp.FLASH, &mut dp.PWR, &mut dp.SYSCFG) 77 | .unwrap(); 78 | 79 | debug_workaround(); 80 | 81 | // Set up pins with appropriate modes. 82 | setup_pins(); 83 | 84 | // The output frequency of the DAC. 85 | let out_wave_freq = 2000.; 86 | let timer_freq = out_wave_freq * LUT_LEN as f32; 87 | 88 | // Tim6 and Tim7 are internally connected to the DAC and are able to drive it through their 89 | // trigger outputs. 90 | // We set this timer to the sample rate in Hz. 91 | // This timer triggers a transfer of one word from the DMA buffer into the DAC output register. 92 | let mut dac_timer = BasicTimer::new(dp.TIM6, TimerDevice::T6, timer_freq, &clock_cfg); 93 | 94 | // The update event is selected as a trigger output (TRGO). For instance a 95 | // master timer can then be used as a prescaler for a slave timer. 96 | dac_timer.set_mastermode(MasterModeSelection::Update); 97 | 98 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 99 | 100 | let mut dac = Dac::new(dp.DAC, Default::default(), 3.3); 101 | dac.calibrate_buffer(DacChannel::C1, &mut delay); 102 | dac.set_trigger(DacChannel::C1, Trigger::Tim6); 103 | 104 | let mut dma = Dma::new(dp.DMA1); 105 | 106 | dma::mux(DmaChannel::C3, dma::DmaInput::DacCh1); 107 | 108 | // Load the Sine LUT into a DMA circular buffer, which will send a 16-byte word of data 109 | // to the DAC on each timer trigger. Because it's a circular buffer, it will start at 110 | // the first value again once complete. 111 | let channel_cfg = dma::ChannelCfg { 112 | circular: dma::Circular::Enabled, 113 | ..Default::default() 114 | }; 115 | 116 | unsafe { 117 | dac.write_dma( 118 | &lut::SIN_X, 119 | DacChannel::C1, 120 | DmaChannel::C3, 121 | channel_cfg, 122 | &mut dma, 123 | ); 124 | } 125 | 126 | dac.enable(DacChannel::C1); 127 | dac_timer.enable(); 128 | 129 | loop { 130 | low_power::csleep(&mut cp.SCB); 131 | } 132 | } 133 | 134 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 135 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 136 | #[defmt::panic_handler] 137 | fn panic() -> ! { 138 | cortex_m::asm::udf() 139 | } 140 | -------------------------------------------------------------------------------- /examples/adc.rs: -------------------------------------------------------------------------------- 1 | //! This module includes an overview of ADC features available. 2 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 3 | 4 | #![no_main] 5 | #![no_std] 6 | 7 | use cortex_m::delay::Delay; 8 | use cortex_m_rt::entry; 9 | use critical_section::with; 10 | use hal::{ 11 | adc::{ 12 | Adc, AdcChannel, AdcDevice, AdcInterrupt, Align, ClockMode, InputType, OperationMode, 13 | SampleTime, 14 | }, 15 | clocks::Clocks, 16 | dma::{self, Dma, DmaChannel, DmaInput, DmaInterrupt, DmaPeriph, DmaWriteBuf}, 17 | gpio::{Pin, PinMode, Port}, 18 | low_power, pac, 19 | prelude::*, 20 | timer::{BasicTimer, MasterModeSelection}, 21 | }; 22 | 23 | static mut ADC_READ_BUF: [u16; 2] = [0; 2]; 24 | 25 | #[entry] 26 | fn main() -> ! { 27 | // Set up CPU peripherals 28 | let mut cp = cortex_m::Peripherals::take().unwrap(); 29 | // Set up microcontroller peripherals 30 | let mut dp = pac::Peripherals::take().unwrap(); 31 | 32 | let clock_cfg = Clocks::default(); 33 | 34 | clock_cfg.setup().unwrap(); 35 | 36 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 37 | 38 | let chan_num = 2; 39 | 40 | // Configure the ADC pin in analog mode. (This is the default state for some STM32 families, 41 | // but not all) 42 | let _adc_pin = Pin::new(Port::B, 0, PinMode::Analog); 43 | 44 | let mut adc = Adc::new_adc1( 45 | dp.ADC1, 46 | AdcDevice::One, 47 | Default::default(), 48 | clock_cfg.systick(), 49 | ); 50 | 51 | // 1: Configuration options: 52 | 53 | // Set a channel to a specific position in a sequence: 54 | adc.set_sequence(1, 2); // Set channel 1 to be the second position in the sequence. 55 | 56 | // Set the length of the sequence to read. (ie number of channels): 57 | adc.set_sequence_len(2); 58 | 59 | // Set up differential mode: 60 | adc.set_input_type(chan_num, InputType::Differential); 61 | 62 | // Change the sample rate: 63 | adc.set_sample_time(chan_num, SampleTime::T2); 64 | 65 | // Set left align mode: 66 | adc.set_align(Align::Left); 67 | 68 | adc.enable_interrupt(AdcInterrupt::EndOfSequence); 69 | 70 | // If you wish to sample at a fixed rate, consider using a basic timer (TIM6 or TIM7) 71 | let mut adc_timer = BasicTimer::new( 72 | dp.TIM6, 100., // Frequency in Hz. 73 | &clock_cfg, 74 | ); 75 | 76 | // The update event is selected as a trigger output (TRGO). For instance a 77 | // master timer can then be used as a prescaler for a slave timer. 78 | adc_timer.set_mastermode(MasterModeSelection::Update); 79 | adc_timer.enable(); 80 | 81 | // todo: Which should it be? 82 | adc.set_trigger(adc::Trigger::Tim6Trgo, adc::TriggerEdge::HardwareRising); 83 | 84 | adc.set_trigger(DacChannel::C1, Trigger::Tim6); 85 | 86 | // 2: Set up DMA, for non-blocking transfers: 87 | let mut dma = Dma::new(&mut dp.DMA1, &dp.RCC); 88 | 89 | let mut dma_buf = [0]; 90 | 91 | dma::mux(DmaChannel::C1, DmaInput::Adc1); 92 | 93 | // Begin a DMA transfer. Note that the `DmaChannel` we pass here is only used on 94 | // MCUs that use `DMAMUX`, eg L5, G0, and G4. For those, you need to run `mux`, to 95 | // set the channel: `dma::mux(DmaPeriph::Dma1, DmaChannel::C1, MuxInput::Adc1); 96 | unsafe { 97 | adc.read_dma( 98 | &mut dma_buf, 99 | &[chan_num], 100 | DmaChannel::C1, 101 | Default::default(), 102 | DmaPeriph::Dma1, 103 | ) 104 | }; 105 | 106 | // Wait for the transfer to complete. Ie by handling the channel's transfer-complete 107 | // interrupt in an ISR, which is enabled by the `read_dma` command. 108 | // For this example, we block until the flag is set. 109 | while !dma.transfer_is_complete(DmaChannel::C1) {} 110 | dma.stop(DmaChannel::C1); 111 | 112 | defmt::println!("Reading: {:?}", &dma_buf[0]); 113 | 114 | // Unmask the interrupt line, and set its priority. See the `DMA_CH1` interrupt handler below. 115 | setup_nvic!([(DMA1_CH1, 3)]); 116 | 117 | // 3: Example of starting a circular DMA transfer using 2 channels. This will continuously update 118 | // the buffer with values from channels 17 and 12. (You can set longer sequence lengths as well). 119 | // You can then read from the buffer at any point to get the latest reading. 120 | let adc_cfg = AdcConfig { 121 | operation_mode: adc::OperationMode::Continuous, 122 | ..Default::default() 123 | }; 124 | 125 | let mut batt_curr_adc = Adc::new_adc2(dp.ADC2, AdcDevice::Two, adc_cfg, clock_cfg.systick()); 126 | 127 | unsafe { 128 | batt_curr_adc.read_dma( 129 | &mut ADC_READ_BUF, 130 | &[17, 12], 131 | DmaChannel::C2, 132 | ChannelCfg { 133 | circular: dma::Circular::Enabled, 134 | ..Default::default() 135 | }, 136 | &mut dma, 137 | ); 138 | } 139 | 140 | // 4: Alternatively, we can take readings without DMA. This provides a simpler, blocking API. 141 | 142 | // Take a blocking reading from channel 3. 143 | let reading = adc.read(chan_num); 144 | 145 | // Convert a reading to voltage, which includes compensation for the built-in VDDA 146 | // reference voltage 147 | let voltage = adc.reading_to_voltage(reading); 148 | 149 | // Or, read convert multiple channels in a sequence. You can read the results once the end- 150 | //-of-sequence interrupt fires. 151 | adc.enable_interrupt(AdcInterrupt::EndOfSequence); 152 | adc.start_conversion(&[1, 2, 3]); 153 | 154 | loop { 155 | low_power::sleep_now(); 156 | } 157 | } 158 | 159 | #[interrupt] 160 | /// This interrupt fires when the ADC transfer is complete. 161 | fn DMA1_CH1() { 162 | dma::clear_interrupt( 163 | DmaPeriph::Dma1, 164 | DmaChannel::C1, 165 | DmaInterrupt::TransferComplete, 166 | ); 167 | 168 | // (Handle the readings as required here. Perhaps filter them, or use them.) 169 | } 170 | 171 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 172 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 173 | #[defmt::panic_handler] 174 | fn panic() -> ! { 175 | cortex_m::asm::udf() 176 | } 177 | -------------------------------------------------------------------------------- /src/flash/mod.rs: -------------------------------------------------------------------------------- 1 | //! Read and write onboard flash memory. Erase pages (sectors on H7), write data, 2 | //! and read data. 3 | //! 4 | //! Before using this module, check the datasheet and/or RM for your specific 5 | //! STM32 variant for page \[sector\] size, and number of pages \[sectors\] available. 6 | //! Make sure not to write to a page your MCU doesn't have, or that includes your 7 | //! program's memory. 8 | 9 | use cfg_if::cfg_if; 10 | 11 | use crate::pac::FLASH; 12 | 13 | // Note that L5 code is simialr to other families like L4 and G4, but splits many options into 14 | // 2 sets; one for secure, one for nonsecure. 15 | 16 | const BANK1_START_ADDR: usize = 0x0800_0000; 17 | 18 | cfg_if! { 19 | if #[cfg(any(feature = "l5", feature = "g473", feature = "g474", feature = "g483", feature = "g484"))] { 20 | const PAGE_SIZE_SINGLE_BANK: usize = 4_096; 21 | const PAGE_SIZE_DUAL_BANK: usize = 2_048; 22 | const BANK2_START_ADDR: usize = 0x0804_0000; 23 | } else if #[cfg(feature = "h7")]{ 24 | const SECTOR_SIZE: usize = 0x2_0000; 25 | const BANK2_START_ADDR: usize = 0x0810_0000; 26 | } else { 27 | const PAGE_SIZE: usize = 2_048; 28 | #[allow(dead_code)] // bank arg on single-bank MCUs. 29 | const BANK2_START_ADDR: usize = 0x0804_0000; 30 | } 31 | } 32 | 33 | cfg_if! { 34 | if #[cfg(any(feature = "l5", feature = "h5"))] { 35 | mod trustzone; 36 | pub use trustzone::*; 37 | } else { 38 | mod non_trustzone; 39 | pub use non_trustzone::*; 40 | } 41 | } 42 | 43 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 44 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 45 | /// Possible error states for flash operations. 46 | pub enum FlashError { 47 | /// Flash controller is not done yet 48 | Busy, 49 | /// Error detected (by command execution, or because no command could be executed) 50 | Illegal, 51 | /// Set during read if ECC decoding logic detects correctable or uncorrectable error 52 | EccError, 53 | /// Page number is out of range 54 | PageOutOfRange, 55 | /// (Legal) command failed 56 | Failure, 57 | } 58 | 59 | pub struct Flash { 60 | pub regs: FLASH, 61 | #[cfg(any( 62 | feature = "g473", 63 | feature = "g474", 64 | feature = "g483", 65 | feature = "g484", 66 | feature = "l5" 67 | ))] 68 | pub dual_bank: DualBank, 69 | } 70 | 71 | // todo: Is H5 more like H7, or the others? 72 | 73 | /// Contains code common to both modules. 74 | impl Flash { 75 | #[cfg(not(any( 76 | feature = "g473", 77 | feature = "g474", 78 | feature = "g483", 79 | feature = "g484", 80 | feature = "l5" 81 | )))] 82 | /// Create a struct used to perform operations on Flash. 83 | pub fn new(regs: FLASH) -> Self { 84 | Self { regs } 85 | } 86 | 87 | #[cfg(any( 88 | feature = "g473", 89 | feature = "g474", 90 | feature = "g483", 91 | feature = "g484", 92 | feature = "l5" 93 | ))] 94 | pub fn new(regs: FLASH, dual_bank: DualBank) -> Self { 95 | // Some G4 variants let you select dual or single-bank mode. via the Option bit. 96 | Self { regs, dual_bank } 97 | } 98 | 99 | /// Read flash memory at a given page and offset into an 8-bit-dword buffer. 100 | #[allow(unused_variables)] // bank arg on single-bank MCUs. 101 | pub fn read(&self, bank: Bank, page: usize, offset: usize, buf: &mut [u8]) { 102 | // H742 RM, section 4.3.8: 103 | // Single read sequence 104 | // The recommended simple read sequence is the following: 105 | // 1. Freely perform read accesses to any AXI-mapped area. 106 | // 2. The embedded Flash memory effectively executes the read operation from the read 107 | // command queue buffer as soon as the non-volatile memory is ready and the previously 108 | // requested operations on this specific bank have been served. 109 | cfg_if! { 110 | if #[cfg(any( 111 | feature = "g473", 112 | feature = "g474", 113 | feature = "g483", 114 | feature = "g484", 115 | feature = "l5", 116 | ))] { 117 | let mut addr = page_to_address(self.dual_bank, bank, page) as *mut u32; 118 | } else if #[cfg(feature = "h7")]{ 119 | let mut addr = page_to_address(bank, page) as *mut u32; 120 | } else { 121 | let mut addr = page_to_address(page) as *mut u32; 122 | } 123 | } 124 | 125 | unsafe { 126 | // Offset it by the start position 127 | addr = unsafe { addr.add(offset) }; 128 | // Iterate on chunks of 32bits 129 | for chunk in buf.chunks_mut(4) { 130 | let word = unsafe { core::ptr::read_volatile(addr) }; 131 | let bytes = word.to_le_bytes(); 132 | 133 | let len = chunk.len(); 134 | if len < 4 { 135 | chunk[0..len].copy_from_slice(&bytes[0..len]); 136 | } else { 137 | chunk[0..4].copy_from_slice(&bytes); 138 | }; 139 | 140 | unsafe { addr = addr.add(1) }; 141 | } 142 | } 143 | } 144 | } 145 | 146 | /// Calculate the address of the start of a given page. Each page is 2,048 Kb for non-H7. 147 | /// For H7, sectors are 128Kb, with 8 sectors per bank. 148 | #[cfg(not(any( 149 | feature = "g473", 150 | feature = "g474", 151 | feature = "g483", 152 | feature = "g484", 153 | feature = "h5", 154 | feature = "l5", 155 | feature = "h7" 156 | )))] 157 | fn page_to_address(page: usize) -> usize { 158 | BANK1_START_ADDR + page * PAGE_SIZE 159 | } 160 | 161 | #[cfg(any( 162 | feature = "g473", 163 | feature = "g474", 164 | feature = "g483", 165 | feature = "g484", 166 | feature = "h5", 167 | feature = "l5", 168 | ))] 169 | fn page_to_address(dual_bank: DualBank, bank: Bank, page: usize) -> usize { 170 | if dual_bank == DualBank::Single { 171 | BANK1_START_ADDR + page * PAGE_SIZE_SINGLE_BANK 172 | } else { 173 | match bank { 174 | Bank::B1 => BANK1_START_ADDR + page * PAGE_SIZE_DUAL_BANK, 175 | Bank::B2 => BANK2_START_ADDR + page * PAGE_SIZE_DUAL_BANK, 176 | } 177 | } 178 | } 179 | 180 | #[cfg(feature = "h7")] 181 | /// Calculate the address of the start of a given page. Each page is 2,048 Kb for non-H7. 182 | /// For H7, sectors are 128Kb, with 8 sectors per bank. 183 | fn page_to_address(bank: Bank, sector: usize) -> usize { 184 | // Note; Named sector on H7. 185 | let starting_pt = match bank { 186 | Bank::B1 => BANK1_START_ADDR, 187 | // todo: This isn't the same bank2 starting point for all H7 variants! 188 | #[cfg(not(any(feature = "h747cm4", feature = "h747cm7")))] 189 | Bank::B2 => BANK2_START_ADDR, 190 | }; 191 | 192 | starting_pt + sector * SECTOR_SIZE 193 | } 194 | -------------------------------------------------------------------------------- /src/usb.rs: -------------------------------------------------------------------------------- 1 | //! USB support, including for simulated COM ports. This module is a thin wrapper required to work with 2 | //! the `stm32_usbd` crate. 3 | //! 4 | //! Requires the `usb` feature. 5 | //! 6 | //! Used on F303, L4x2, L4x3, L4x5, L5, G0, and G4. F4, L4x6 and H7 use the `usb_otg` module. 7 | //! For G0 series, USB is only available on G0B0, G0B1, G0C1, which the PAC doesn't yet differentiate, 8 | //! and this library doesn't yet support. 9 | 10 | /* 11 | Small caveat, the pac for the l4x5 exposes a USB peripheral instead 12 | of a OTG peripheral, even though this chart 13 | https://www.st.com/en/microcontrollers-microprocessors/stm32l4-series.html 14 | shows that the stm32l475 has OTG. 15 | 16 | Further inspection shows that the generated pac for the l4x5 has a USB peripheral 17 | while the l4r5 has an OTG, but this crate doesn't currently seem to support the 18 | l4r5 variant. 19 | 20 | Strangely, the register modification commands for the l4x5 have OTG in their names 21 | */ 22 | 23 | use core::borrow::BorrowMut; 24 | 25 | use cfg_if::cfg_if; 26 | pub use stm32_usbd::UsbBus; 27 | use stm32_usbd::{MemoryAccess, UsbPeripheral}; 28 | 29 | // use usb_device::{bus::UsbBusAllocator, prelude::*}; 30 | // use usb_device::class_prelude::UsbBus as UsbBus_; 31 | // use usbd_serial::{self, SerialPort}; 32 | #[cfg(any(feature = "l4", feature = "l5", feature = "g0"))] 33 | use crate::pac::PWR; 34 | use crate::{pac, pac::USB, util::rcc_en_reset}; 35 | 36 | /// Represents a Universal Serial Bus (USB) peripheral. Functionality is implemented through the 37 | /// implemented `stm32_usbd::UsbPeripheral` trait. 38 | pub struct Peripheral { 39 | /// USB Register Block 40 | pub regs: USB, 41 | } 42 | 43 | unsafe impl Sync for Peripheral {} 44 | // unsafe impl Send for Peripheral {} // todo: Required/do we want this? 45 | 46 | unsafe impl UsbPeripheral for Peripheral { 47 | const REGISTERS: *const () = USB::ptr() as *const (); 48 | 49 | // Embedded pull-up resistor on USB_DP line 50 | #[cfg(feature = "f3")] 51 | const DP_PULL_UP_FEATURE: bool = false; 52 | 53 | #[cfg(not(feature = "f3"))] 54 | const DP_PULL_UP_FEATURE: bool = true; 55 | 56 | // Pointer to the endpoint memory 57 | // todo: This is the L4 setting. Is this right? 58 | // L4 Reference manual, Table 2. USB SRAM is on APB1, at this address: 59 | #[cfg(any(feature = "l4", feature = "wb"))] 60 | const EP_MEMORY: *const () = 0x4000_6c00 as _; 61 | 62 | #[cfg(feature = "l5")] 63 | const EP_MEMORY: *const () = 0x5000_d800 as _; 64 | 65 | #[cfg(feature = "g0")] 66 | const EP_MEMORY: *const () = 0x4000_5c00 as _; 67 | 68 | #[cfg(feature = "c0")] 69 | const EP_MEMORY: *const () = 0x4000_9800 as _; // Table 7, USBRAM entry 70 | 71 | #[cfg(any(feature = "f3", feature = "g4"))] 72 | const EP_MEMORY: *const () = 0x4000_6000 as _; 73 | 74 | // Endpoint memory size in bytes 75 | // F303 subvariants have diff mem sizes and bits/word scheme: 0B/C use 512 size and 1x16 bits/word. 76 | // F303D/E use 1024 bytes, and 2x16 bits/word. 77 | #[cfg(feature = "f3")] 78 | const EP_MEMORY_SIZE: usize = 512; 79 | // todo: Feature-gate various memory sizes 80 | 81 | #[cfg(any(feature = "l4", feature = "l5", feature = "g4", feature = "wb"))] 82 | const EP_MEMORY_SIZE: usize = 1_024; 83 | 84 | #[cfg(feature = "g0")] 85 | const EP_MEMORY_SIZE: usize = 2_048; 86 | 87 | #[cfg(feature = "c0")] 88 | const EP_MEMORY_SIZE: usize = 2_048; // Table 154. 89 | 90 | // Endpoint memory access scheme. 91 | // Set to `true` if "2x16 bits/word" access scheme is used, otherwise set to `false`. 92 | #[cfg(any(feature = "l4", feature = "l5", feature = "g4", feature = "wb"))] 93 | const EP_MEMORY_ACCESS: MemoryAccess = MemoryAccess::Word16x2; 94 | 95 | #[cfg(any(feature = "f3", feature = "g0"))] 96 | // F3 uses 1x16 or 2x16 depending on variant. G0 uses a 32-bit word. 97 | const EP_MEMORY_ACCESS: MemoryAccess = MemoryAccess::Word16x2; 98 | 99 | #[cfg(any(feature = "c0", feature = "h5"))] 100 | const EP_MEMORY_ACCESS: MemoryAccess = MemoryAccess::Word32x1; 101 | 102 | fn enable() { 103 | let rcc = unsafe { &*pac::RCC::ptr() }; 104 | 105 | cfg_if! { 106 | if #[cfg(feature = "l4")] { 107 | cfg_if! { 108 | if #[cfg(feature = "l4x5")] { 109 | rcc_en_reset!(ahb2, otgfs, rcc); // Why does the l4x5 have USB peripheral but OTG names? 110 | } else { 111 | rcc_en_reset!(apb1, usbfs, rcc); 112 | } 113 | } 114 | } else if #[cfg(feature = "l5")] { 115 | rcc.apb1enr2().modify(|_, w| w.usbfsen().bit(true)); 116 | rcc.apb1rstr2().modify(|_, w| w.usbfsrst().bit(true)); 117 | rcc.apb1rstr2().modify(|_ , w| w.usbfsrst().clear_bit()); 118 | } else if #[cfg(feature = "wb")] { 119 | rcc.apb1enr1().modify(|_, w| w.usben().bit(true)); 120 | rcc.apb1rstr1().modify(|_, w| w.usbfsrst().bit(true)); 121 | rcc.apb1rstr1().modify(|_ , w| w.usbfsrst().clear_bit()); 122 | } else { // G0, G4 123 | rcc_en_reset!(apb1, usb, rcc); 124 | } 125 | } 126 | } 127 | 128 | fn startup_delay() { 129 | // There is a chip specific startup delay, around 1µs. 130 | // todo: Come back to this. This value current assumes 1µs 131 | // todo for an L4 running at full speed. 132 | cortex_m::asm::delay(80); 133 | } 134 | } 135 | 136 | /// Type of the UsbBus 137 | pub type UsbBusType = UsbBus; 138 | 139 | #[cfg(any(feature = "l4", feature = "l5", feature = "g0"))] 140 | /// Enables the Vdd USB power supply. Note that we also need to enable `PWREN` in APB1, 141 | /// but we handle this using the RTC setup. Use a raw pointer if doing this without the RTC 142 | /// already set up. 143 | pub fn enable_usb_pwr() { 144 | // Enable VddUSB 145 | let pwr = unsafe { &*pac::PWR::ptr() }; 146 | pwr.cr2().modify(|_, w| w.usv().bit(true)); 147 | } 148 | 149 | // todo: We need to sort out the SerialPort traits to makme this work. Non-trivial. 150 | 151 | // /// Helper to handle chunked USB writing. Blocks. 152 | // pub fn write_usb, WS: BorrowMut<[u8]>>( 153 | // usb_serial: SerialPort<'static, B, RS, WS>, 154 | // usb_dev: &mut UsbDevice<'static, UsbBusType>, 155 | // buf: &[u8], 156 | // msg_len: usize 157 | // ) { 158 | // let mut offset = 0; 159 | // while offset < msg_len { 160 | // match usb_serial.write(&buf[offset..msg_len]) { 161 | // Ok(0) | Err(UsbError::WouldBlock) => { 162 | // usb_dev.poll(&mut [usb_serial]); 163 | // } 164 | // Ok(written) => offset += written, 165 | // Err(e) => { 166 | // // defmt::warn!("USB write error: {:?}", e); 167 | // break; 168 | // } 169 | // } 170 | // } 171 | 172 | // while usb_serial.flush().err() == Some(UsbError::WouldBlock) { 173 | // usb_dev.poll(&mut [usb_serial]); 174 | // } 175 | // } 176 | -------------------------------------------------------------------------------- /examples/clock_cfg.rs: -------------------------------------------------------------------------------- 1 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 2 | //! Clock config varies significantly by family 3 | //! Reference the Cube Mx interactive clock tree tool, or the clock tree in the reference 4 | //! manual for a visual overview. The above code lines are not valid for all STM32 families 5 | //! supported by this crate. 6 | //! 7 | //! Note that the Rust docs are built for L4, and may not be accurate for other MCUs. 8 | 9 | #![no_main] 10 | #![no_std] 11 | 12 | use cortex_m_rt::entry; 13 | use hal::{ 14 | clocks::{self, ApbPrescaler, Clocks, InputSrc, MsiRng, PllCfg, PllSrc, Pllm, Pllr}, 15 | low_power, pac, 16 | }; 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | // Set up CPU peripherals 21 | let mut cp = cortex_m::Peripherals::take().unwrap(); 22 | // Set up microcontroller peripherals 23 | let mut dp = pac::Peripherals::take().unwrap(); 24 | 25 | // Set up a default setting. See documentation on Rust docs for details about 26 | // what this does (it depends on the MCU), but it usually runs the core and most 27 | // peripheral clocks at the max rated speed, using HSI as the input source. 28 | // Unfortunately, the docs hosted on `docs.rs` can only show a single variant, so 29 | // will likely be innacurate. Inspect the [clocks modules here](https://github.com/David-OConnor/stm32-hal/tree/main/src/clocks) 30 | // for details. 31 | 32 | // The default clock setting will (for most variants) enable the (or a) PLL, and configure 33 | // scalers so system clock is at its maximum speed, using the internal oscillator (HSE). 34 | // Most H7 variants default to their non-boosted 35 | // speed, ie either 400Mhz or 520Mhz (not 480 or 550Mhz). 36 | let mut clock_cfg = Clocks::default(); 37 | 38 | // An example modifying some settings, while leaving the rest default. This is the intended 39 | // pattern. 40 | let clock_cfg = Clocks { 41 | input_src: InputSrc::Hsi, 42 | stop_wuck: StopWuck::Hsi, 43 | ..Default::default() 44 | }; 45 | 46 | // Here's an example clock config, where you're using a 16Mhz SMD oscillator (meaning we can enable 47 | // HSE bypass), and are using the internal HSI48 for USB. This also demonstrates how to configure 48 | // an external oscillator. (HSE) 49 | let clock_cfg = Clocks { 50 | input_src: InputSrc::Pll(PllSrc::Hse(16_000_000)), 51 | hse_bypass: true, 52 | hsi48_on: true, 53 | clk48_src: Clk48Src::Hsi48, 54 | ..Default::default() 55 | }; 56 | 57 | // If you'd prefer, you can modify fields individually from a mutable clock config, instead 58 | // of using the code pattern above. 59 | 60 | // Set it up to use the HSI directly, with no PLL. This will result in a reduced speed: 61 | clock_cfg.input_src = InputSrc::Hsi; 62 | 63 | // Set up to use a 8MHze HSE, with no PLL. 64 | clock_cfg.input_src = InputSrc::Hse(8_000_000); 65 | 66 | // If you set a 8Mhz HSE as above, you may need to reduce the default PLLM value to compensate 67 | // compared to a 16Mhz HSI: 68 | clock_cfg.pll.divm = Pllm::Div2; 69 | 70 | // Enable PLLQ etc 71 | clocks_cfg.pll.pllp_en = true; 72 | clocks_cfg.pll.divp = Pllr::Div8; 73 | 74 | // Note that on H7, we have 3 separate PLLs we can configure, at the `pll1`, `pll2`, and `pll3` fields. 75 | // L4 and WB have a second PLL, called `PLLSAI`, primarily intended for the SAI audio peripheral. 76 | // Its config field is `pllsai`. (and `pllsai2` for L4x5 and L4x6). 77 | // For example, here's a PLL config on H7 that sets PLL2P as the SAI2 source, and configures 78 | // its speed. (By default, only PLL1(R) is enabled. 79 | let clock_cfg = Clocks { 80 | pll2: PllCfg { 81 | enabled: true, 82 | pllp_en: true, 83 | divn: 99, 84 | divp: 16, 85 | ..PllCfg::disabled() 86 | }, 87 | sai1_src: SaiSrc::Pll2P, 88 | ..Default::default() 89 | }; 90 | 91 | // Or on L4 or WB, using the PLLSAI: 92 | let clock_cfg = Clocks { 93 | pllsai1: PllCfg { 94 | enabled: true, 95 | pllp_en: true, 96 | divn: 32, 97 | ..PllCfg::disabled() 98 | }, 99 | sai1_src: SaiSrc::PllSai1P, // Note that thsi is the default, but you can change it. 100 | ..Default::default() 101 | }; 102 | 103 | // Note that H7 also lets you select VOS range, and will give you an error if you select an 104 | // invalid range for your HCLK speed. VOS0 is required for full speed. 105 | // clock_cfg.vos_range = VosRange::VOS0; 106 | // You can use `Clocks::full_speed()` to configure an H743 etc at 480Mhz. 107 | 108 | // Change the default wakeup from Stop mode to be HSI instead of MSI. (L4 and L5 only) 109 | clock_cfg.stop_wuck = StopWuck::Hsi; 110 | 111 | // Set up PLL using a 4Mhz HSE: 112 | clock_cfg.input_src = InputSrc::Pll(PllSrc::Hse(4_000_000)); 113 | 114 | // Enable the Clock Security System (CSS) 115 | clock_cfg.security_system = true; 116 | 117 | // Bypass HSE output 118 | clock_cfg.hse_bypass = true; 119 | 120 | // Enable HSI48 (eg L4, L5, G4 etc) 121 | clock_cfg.hse48_on = true; 122 | 123 | // Change MSI speed (L4, L5 only) 124 | clock_cfg.change_msi_speed(MsiRange::R2M); 125 | 126 | // (L4 and L5 only) If you'd like to use MSI for the USB clock source, run this function. 127 | // Do not run it if using MSI for the input source or PLL source. You must also have 128 | // `clk48_src: Clk48Src::MSI` in the clock cfg, which is the default for L4 and L5. 129 | clocks_cfg.enable_msi_48(); 130 | 131 | // Change PLL prescalers: 132 | clock_cfg.pllm = Pllm::Div4; 133 | clock_cfg.plln = 22; 134 | clock_cfg.pllr = Pllm::Div4; 135 | 136 | // Change some of the peripheral prescalers 137 | clock_cfg.apb1prescaler = ApbPrescaler::Div2; 138 | 139 | // Enable the Clock Recovery System (CRS), to automatically trim the HSI48 on variants 140 | // that include it. (eg STM32l4x2 and L4x3, L5, G4, and H7) 141 | clocks::enable_crs(CrsSyncSrc::Usb); // CrsSyncSrc::OtgHs on H7. 142 | 143 | // If you need to modify functionality not supported by this library, 144 | // you can make register writes directly using the PAC. If you find missing functionality 145 | // you find useful, consider making an issue or PR on Github. 146 | // For example, to set I2C1 to use HSI as its source, on L4: 147 | dp.RCC 148 | .ccipr 149 | .modify(|_, w| unsafe { w.i2c1sel().bits(0b10) }); 150 | 151 | // Configure clock registers. The previous creation and modification of `clock_cfg` 152 | // only set up a configuration struct; `Clocks::setup` performs the MCU operations. 153 | clock_cfg.setup().unwrap(); 154 | 155 | // Show speeds. 156 | defmt::println!("Speeds: {:?}", clock_cfg.calc_speeds()); 157 | 158 | loop { 159 | low_power::sleep_now(); 160 | } 161 | } 162 | 163 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 164 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 165 | #[defmt::panic_handler] 166 | fn panic() -> ! { 167 | cortex_m::asm::udf() 168 | } 169 | -------------------------------------------------------------------------------- /src/ethernet.rs: -------------------------------------------------------------------------------- 1 | //! WIP. This module contains ethernet code for the H7, for use with its Synopsys ethernet 2 | //! hardware. 3 | //! With the `network` feature enabled, allows support for the [smoltcp stack](https://docs.rs/smoltcp/latest/smoltcp/). 4 | //! See H743 RM, chapter 58. 5 | //! https://www.keil.com/pack/doc/CMSIS/Driver/html/group__eth__interface__gr.html ? 6 | 7 | use core::ops::Deref; 8 | 9 | use critical_section::with; 10 | use smoltcp::{ 11 | self, 12 | phy::{self, Device, DeviceCapabilities, Medium}, 13 | time::Instant, 14 | }; 15 | 16 | use crate::{ 17 | error::Result, 18 | pac::{self, ETHERNET_DMA, ETHERNET_MAC, ETHERNET_MTL, RCC}, 19 | util::RccPeriph, 20 | }; 21 | 22 | /// Configuration data for Ethernet 23 | pub struct EthConfig {} 24 | 25 | // impl Default for EthConfig { 26 | // fn default() -> Self { 27 | // Self { 28 | // mode: SpiMode::mode0(), 29 | // comm_mode: SpiCommMode::FullDuplex, 30 | // slave_select: SlaveSelect::Software, 31 | // data_size: DataSize::D8, 32 | // fifo_reception_thresh: ReceptionThresh::D8, 33 | // } 34 | // } 35 | // } 36 | 37 | /// Represents an ethernet peripheral. 38 | pub struct Eth { 39 | pub regs_dma: RDma, 40 | pub regs_mac: RMac, 41 | pub regs_mtl: RMtl, 42 | pub cfg: EthConfig, 43 | } 44 | 45 | impl Eth 46 | where 47 | RDma: Deref + RccPeriph, 48 | RMac: Deref + RccPeriph, 49 | RMtl: Deref + RccPeriph, 50 | { 51 | /// Initialize an ethernet peripheral, including configuration register writes, and enabling and resetting 52 | /// its RCC peripheral clock. 53 | pub fn new(regs: R, cfg: EthConfig) -> Self { 54 | with(|_| { 55 | let rcc = unsafe { &(*RCC::ptr()) }; 56 | R::en_reset(rcc); 57 | }); 58 | 59 | Self { regs, cfg } 60 | } 61 | 62 | /// H743 RM, section 58.9.1: DMA initialization 63 | pub fn init_dma(&mut self) -> Result<()> { 64 | // Complete the following steps to initialize the DMA: 65 | 66 | // 1. Provide a software reset to reset all MAC internal registers and logic (bit 0 of DMA 67 | // mode register (ETH_DMAMR)). 68 | self.regs_dma.dmamr.write(|w| w.swr().bit(true)); 69 | 70 | // 2. Wait for the completion of the reset process (poll bit 0 of the DMA mode register 71 | // (ETH_DMAMR), which is cleared when the reset operation is completed). 72 | // todo: Use DmaError instead? 73 | bounded_loop!( 74 | self.regs_dma.dmamr.read().swr().bit_is_set(), 75 | Error::RegisterUnchanged 76 | ); 77 | 78 | // 3. Program the following fields to initialize the System bus mode register 79 | // (ETH_DMASBMR): 80 | self.regs_dma.dmasbmr.modify(|_, w| { 81 | // todo 82 | // a)AAL 83 | w.aal.bits(0); 84 | // b) Fixed burst or undefined burst 85 | w.aal.bits(0); 86 | // c) Burst mode values in case of AHB bus interface. 87 | w.aal.bits(0); 88 | // d) If fixed length value is enabled, select the maximum burst length possible on the 89 | // AXI Bus (bits [7:1]) 90 | w.aal.bits(0) 91 | }); 92 | 93 | // 4. Create a transmit and a receive descriptor list. In addition, ensure that the receive 94 | // descriptors are owned by the DMA (set bit 31 of TDES3/RDES3 descriptor). For more 95 | // information on descriptors, refer to Section 58.10: Descriptors. 96 | // Note: 97 | // Descriptor address from start to end of the ring should not cross the 4GB boundary. 98 | // 99 | // 5. 100 | // Program ETH_DMACTXRLR and ETH_DMACRXRLR registers (see Channel Tx 101 | // descriptor ring length register (ETH_DMACTXRLR) and Channel Rx descriptor ring 102 | // length register (ETH_DMACRXRLR)). The programmed ring length must be at least 4. 103 | // 104 | // 6. Initialize receive and transmit descriptor list address with the base address of transmit 105 | // and receive descriptor (Channel Tx descriptor list address register 106 | // (ETH_DMACTXDLAR), Channel Rx descriptor list address register 107 | // (ETH_DMACRXDLAR)). In addition, program the transmit and receive tail pointer 108 | // registers that inform the DMA about the available descriptors (see Channel Tx 109 | // descriptor tail pointer register (ETH_DMACTXDTPR) and Channel Rx descriptor tail 110 | // pointer register (ETH_DMACRXDTPR)). 111 | // 112 | // 7. Program ETH_DMACCR, ETH_DMACTXCR and ETH_DMACRXCR registers (see 113 | // Channel control register (ETH_DMACCR) and Channel transmit control register 114 | // (ETH_DMACTXCR)) to configure the parameters such as the maximum burst-length 115 | // (PBL) initiated by the DMA, descriptor skip lengths, OSP for TxDMA, RBSZ for 116 | // RxDMA. 117 | // 118 | // 8. Enable the interrupts by programming the ETH_DMACIER register (see Channel 119 | // interrupt enable register (ETH_DMACIER)). 120 | // 121 | // 9. Start the Receive and Transmit DMAs by setting SR (bit 0) of Channel receive control 122 | // register (ETH_DMACRXCR) and ST (bit 0) of the ETH_DMACTXCR (see Channel 123 | // transmit control register (ETH_DMACTXCR)). 124 | } 125 | 126 | /// H743 RM, section 58.9.2: MTL initialization 127 | pub fn init_mtl(&mut self) { 128 | // Complete the following steps to initialize the MTL registers: 129 | 130 | // 1. Program the following fields to initialize the operating mode in the ETH_MTLTXQOMR 131 | // (see Tx queue operating mode Register (ETH_MTLTXQOMR)). 132 | self.regs_mtl.mtltx_qomr.modify(|_, w| { 133 | // a) Transmit Store And Forward (TSF) or Transmit Threshold Control (TTC) if the 134 | // Threshold mode is used. 135 | w.tsf().bits(0); 136 | // b) Transmit Queue Enable (TXQEN) to value 2‘b10 to enable Transmit Queue 0. 137 | w.txqen().bits(0); 138 | // c) Transmit Queue Size (TQS). 139 | w.tqs().bits(0) 140 | }); 141 | 142 | // 2. Program the following fields to initialize the operating mode in the ETH_MTLRXQOMR 143 | // register (see Rx queue operating mode register (ETH_MTLRXQOMR)): 144 | self.regs_mtl.mtltx_qomr.modify(|_, w| { 145 | // a) Receive Store and Forward (RSF) or RTC if Threshold mode is used. 146 | w.rsf().bits(0); 147 | // b) Flow Control Activation and De-activation thresholds for MTL Receive FIFO (RFA 148 | // and RFD). 149 | w.rfa().bits(0); 150 | w.rfd().bits(0); 151 | // c) Error Packet and undersized good Packet forwarding enable (FEP and FUP). 152 | w.fep().bits(0); 153 | w.fup().bits(0); 154 | // d)Receive Queue Size (RQS). 155 | w.rqs().bits(0) 156 | }); 157 | } 158 | 159 | /// H743 RM, section 58.9.3: MAC initialization 160 | pub fn init_mac(&mut self) {} 161 | } 162 | -------------------------------------------------------------------------------- /src/hsem.rs: -------------------------------------------------------------------------------- 1 | //! Hardware semaphore (HSEM) 2 | //! Used on STM32WB to synchronize processes running on different cores. 3 | 4 | use paste::paste; 5 | 6 | use crate::pac::{self, HSEM, RCC}; 7 | 8 | #[derive(Clone, Copy)] 9 | /// The core that's performing the requested operation. Core 1 is the M4 core, and Core 2 is the M0+ core. 10 | pub enum Core { 11 | // todo: This is the same as ipcc::Core; DRY; keep in one place and import in the other/both? 12 | C1, 13 | C2, 14 | } 15 | 16 | pub struct Hsem { 17 | regs: HSEM, 18 | } 19 | 20 | // Helper, since we need to access one of to 31 similarly-named registers. 21 | macro_rules! set_register_sem { 22 | ($semaphore_num:expr, $regs:expr, $core_id:expr, $proc_id:expr) => { 23 | paste! { 24 | $regs.[].modify(|_, w| { 25 | w.procid().bits($proc_id); 26 | w.coreid().bits($core_id); 27 | w.lock().bit(true) 28 | }) 29 | } 30 | }; 31 | } 32 | 33 | /// Represents an Hardware Semiphore (HSEM) peripheral. 34 | impl Hsem { 35 | pub fn new(regs: HSEM) -> Self { 36 | let mut rcc = unsafe { &(*RCC::ptr()) }; 37 | 38 | rcc.ahb3enr().modify(|_, w| w.hsemen().bit(true)); 39 | rcc.ahb3rstr().modify(|_, w| w.hsemrst().bit(true)); 40 | rcc.ahb3rstr().modify(|_, w| w.hsemrst().clear_bit()); 41 | 42 | // todo: Why are these missing here and on IPCC `new`? 43 | // rcc.ahb4enr().modify(|_, w| w.hsemen().bit(true)); 44 | // rcc.ahb4rstr().modify(|_, w| w.hsemrst().bit(true)); 45 | // rcc.ahb4rstr().modify(|_, w| w.hsemrst().clear_bit()); 46 | 47 | Self { regs } 48 | } 49 | 50 | /// RM: The 2-step lock procedure consists in a write to lock the semaphore, followed by a read to 51 | /// check if the lock has been successful, carried out from the HSEM_Rx register 52 | pub fn lock_2_step(&mut self, core: Core, semaphore_num: u8) { 53 | if semaphore_num > 31 { 54 | panic!("Semaphore number must be 0 - 31.") 55 | } 56 | 57 | // todo: You need a macro to do this! Currently only works on semaphore 1. 58 | 59 | let core_id = 0; // todo temp! 60 | let proc_id = 0; // todo temp! 61 | 62 | // * Write semaphore with PROCID and COREID, and LOCK = 1. The COREID data 63 | // written by software must match the AHB bus master information. i.e. a AHB bus master 64 | // set_register_sem!(semaphore_num, self.regs, core_id, proc_id); // todo problem with macro syntax 65 | 66 | // ID = 1writes data COREID = 1. 67 | // Lock is put in place when the semaphore is free at write time. 68 | // * Read-back the semaphore 69 | // The software checks the lock status, if PROCID and COREID match the written data, 70 | // then the lock is confirmed. 71 | // * Else retry (the semaphore has been locked by another process, AHB bus master ID). 72 | } 73 | 74 | /// RM: The 1-step procedure consists in a read to lock and check the semaphore in a single step, 75 | /// carried out from the HSEM_RLRx register. 76 | pub fn lock_1_step(&mut self, core: Core, semaphore_num: u8) { 77 | if semaphore_num > 31 { 78 | panic!("Semaphore number must be 0 - 31.") 79 | } 80 | // * Read lock semaphore with the AHB bus master COREID. 81 | // * If read COREID matches and PROCID = 0, then lock is put in place. If COREID 82 | // matches and PROCID is not 0, this means that another process from the same 83 | // COREID has locked the semaphore with a 2-step (write) procedure. 84 | // * Else retry (the semaphore has been locked by another process, AHB bus master ID). 85 | // A semaphore can only be locked when it is free. When read locking a free semaphore, 86 | // PROCID is 0. Read locking a locked semaphore returns the COREID and PROCID that 87 | // locked it. All read locks, including the first one that locks the semaphore, return the COREID 88 | // that locks or locked the semaphore. 89 | } 90 | 91 | /// Unlock a semaphore. 92 | pub fn unlock(&self, core: Core, semaphore_num: u8) { 93 | if semaphore_num > 31 { 94 | panic!("Semaphore number must be 0 - 31.") 95 | } 96 | // RM: 38.3.5: Unlocking a semaphore is a protected process, to prevent accidental clearing by a AHB bus 97 | // master ID or by a process not having the semaphore lock right. The procedure consists in 98 | // writing to the semaphore HSEM_Rx register with the corresponding COREID and PROCID 99 | // and LOCK = 0. When unlocked the semaphore, the COREID, and the PROCID are all 0. 100 | // When unlocked, an interrupt may be generated to signal the event. To this end, the 101 | // semaphore interrupt shall be enabled. 102 | // The unlock procedure consists in a write to the semaphore HSEM_Rx register with 103 | // matching COREID regardless on how the semaphore has been locked (1-step or 2-step). 104 | //  Write semaphore with PROCID, COREID, and LOCK = 0 105 | //  If the written data matches the semaphore PROCID and COREID and the AHB bus 106 | // master ID , the semaphore is unlocked and an interrupt may be generated when 107 | // enabled, else write is ignored, semaphore remains locked and no interrupt is generated 108 | // (the semaphore is locked by another process, AHB bus master ID or the written data 109 | // does not match the AHB bus master signaling). 110 | } 111 | 112 | /// Enable an interrupt. 113 | pub fn enable_interrupt(&mut self, core: Core, semaphore_num: u8) { 114 | if semaphore_num > 31 { 115 | panic!("Semaphore number must be 0 - 31.") 116 | } 117 | // Cnier doesn't have individual fields 118 | match core { 119 | Core::C1 => { 120 | let orig_value = self.regs.c1ier().read().bits(); 121 | self.regs 122 | .c1ier() 123 | .write(|w| unsafe { w.bits(orig_value | (1 << semaphore_num)) }); 124 | } 125 | Core::C2 => { 126 | let orig_value = self.regs.c2ier().read().bits(); 127 | self.regs 128 | .c2ier() 129 | .write(|w| unsafe { w.bits(orig_value | (1 << semaphore_num)) }); 130 | } 131 | } 132 | } 133 | 134 | /// Clear an interrupt flag - run this in the interrupt's handler to prevent 135 | /// repeat firings. 136 | pub fn clear_interrupt(&mut self, core: Core, semaphore_num: u8) { 137 | if semaphore_num > 31 { 138 | panic!("Semaphore number must be 0 - 31.") 139 | } 140 | // todo: Do we need to read, or can we just do a write of the relevant bit 141 | match core { 142 | Core::C1 => { 143 | let orig_value = self.regs.c1icr().read().bits(); 144 | self.regs 145 | .c1icr() 146 | .write(|w| unsafe { w.bits(orig_value | (1 << semaphore_num)) }); 147 | } 148 | Core::C2 => { 149 | let orig_value = self.regs.c2icr().read().bits(); 150 | self.regs 151 | .c2icr() 152 | .write(|w| unsafe { w.bits(orig_value | (1 << semaphore_num)) }); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stm32-hal2" 3 | version = "2.1.8" 4 | edition = "2024" 5 | authors = ["David O'Connor "] 6 | description = "Hardware abstraction layer for the STM32 MCUs" 7 | keywords = ["no-std", "stm32", "embedded", "embedded-hal"] 8 | categories = [ 9 | "embedded", 10 | "hardware-support", 11 | "no-std", 12 | ] 13 | repository = "https://github.com/David-OConnor/stm32-hal" 14 | documentation = "https://docs.rs/stm32-hal2" 15 | readme = "README.md" 16 | license = "MIT" 17 | exclude = [".gitignore"] 18 | 19 | 20 | [dependencies] 21 | cortex-m = "0.7.7" 22 | 23 | # Peripheral Access Crates 24 | stm32f3 = { version = "0.16.0", optional = true } 25 | stm32f4 = { version = "0.16.0", optional = true } 26 | stm32l4 = { version = "0.16.0", optional = true } 27 | stm32l5 = { version = "0.16.0", optional = true } 28 | stm32g0 = { version = "0.16.0", optional = true } 29 | stm32g4 = { version = "0.16.0", optional = true } 30 | stm32h5 = { version = "0.16.0", optional = true } 31 | stm32h7 = { version = "0.16.0", optional = true } 32 | stm32wb = { version = "0.16.0", optional = true } 33 | stm32wl = { version = "0.16.0", optional = true } 34 | stm32c0 = { version = "0.16.0", optional = true } 35 | 36 | defmt = { version = "1.0.1", optional = true } 37 | 38 | # Embedded-HAL traits and related libs. Featured-gated with `embedded-hal`. 39 | embedded-hal = { version = "1.0.0", optional = true } 40 | embedded-io = { version = "0.6.1", optional = true } 41 | 42 | # Enabled with the `monotonic` feature. 43 | rtic-monotonic = { version = "1.0.0", optional = true } 44 | 45 | # Chrono allows for basic time and date functionality, for use with the RTC. 46 | chrono = { version = "0.4.23", default-features = false } 47 | 48 | # These USB and CAN crates are only imported if one of the `can`, `usb`, `usbotg_fs`, or `usbotg_hs` 49 | # features are used. 50 | # Workaround using a fork of stm32-usbd until the main one is released on crates. 51 | 52 | # These three for non-synopsis. todo: Add in for synopsis, along with our write helper, if applicable. 53 | stm32-usbd = { version = "0.8.0", optional = true } 54 | # usb-device = { version = "0.3.2", optional = true} 55 | # usbd-serial = { version = "0.2.2", optional = true} 56 | 57 | synopsys-usb-otg = { version = "0.4.0", features = ["cortex-m"], optional = true } 58 | 59 | bxcan = { version = "0.8.0", optional = true } 60 | fdcan = { version = "0.2.1", optional = true } 61 | 62 | # TCP stack for use with the Ethernet peripheral. 63 | smoltcp = { version = "0.12.0", optional = true } 64 | 65 | # Misc features 66 | num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } # For sqrt in timers 67 | 68 | # These are for Meta-programming; mainly used for feature-gating 69 | cfg-if = "1.0.0" # if/else blocks for feature-gating 70 | paste = "1.0.15" # Token pasting, to make macros easier 71 | 72 | [build-dependencies] 73 | cfg_aliases = "0.2.1" 74 | 75 | 76 | [package.metadata.docs.rs] 77 | #features = ["h735", "h7rt", "usbotg_hs", "can_fd_h", "embedded_hal"] 78 | features = ["g431", "g4rt", "usb", "can_fd_g", "embedded_hal"] 79 | targets = ["thumbv7em-none-eabihf"] 80 | 81 | 82 | # Compile with one of these "runtime" features if the program 83 | # will be directly flashed to an MCU; ie not a library. 84 | [features] 85 | default = ["defmt"] 86 | 87 | f3rt = ["stm32f3/rt"] 88 | f4rt = ["stm32f4/rt"] 89 | l4rt = ["stm32l4/rt"] 90 | l5rt = ["stm32l5/rt"] 91 | g0rt = ["stm32g0/rt"] 92 | g4rt = ["stm32g4/rt"] 93 | h5rt = ["stm32h5/rt"] 94 | h7rt = ["stm32h7/rt"] 95 | wbrt = ["stm32wb/rt"] 96 | wlrt = ["stm32wl/rt"] 97 | c0rt = ["stm32c0/rt"] 98 | 99 | # Features defined here are those taken from the SVD-generated STM32 PACs. 100 | # The "f3" etc designator at the end lets us group by family when feature-gating. 101 | 102 | # [F3](https://docs.rs/crate/stm32f3/latest/source/Cargo.toml) 103 | f301 = ["stm32f3/stm32f301", "f3"] 104 | f302 = ["stm32f3/stm32f302", "f3"] 105 | f303 = ["stm32f3/stm32f303", "f3"] 106 | f373 = ["stm32f3/stm32f373", "f3"] 107 | f3x4 = ["stm32f3/stm32f3x4", "f3"] 108 | 109 | # [F4](https://docs.rs/crate/stm32f4/latest/source/Cargo.toml) 110 | f401 = ["stm32f4/stm32f401", "f4"] 111 | f405 = ["stm32f4/stm32f405", "f4"] 112 | f407 = ["stm32f4/stm32f407", "f4"] 113 | f410 = ["stm32f4/stm32f410", "f4"] 114 | f411 = ["stm32f4/stm32f411", "f4"] 115 | f412 = ["stm32f4/stm32f412", "f4"] 116 | f413 = ["stm32f4/stm32f413", "f4"] 117 | f427 = ["stm32f4/stm32f427", "f4"] 118 | f429 = ["stm32f4/stm32f429", "f4"] 119 | f446 = ["stm32f4/stm32f446", "f4"] 120 | f469 = ["stm32f4/stm32f469", "f4"] 121 | 122 | # [L4](https://docs.rs/crate/stm32l4/latest/source/Cargo.toml) 123 | l4x1 = ["stm32l4/stm32l4x1", "l4"] 124 | l4x2 = ["stm32l4/stm32l4x2", "l4"] 125 | # L412 is the same as L4x2, but with an RTC like in G0, G4 and L5. 126 | l412 = ["stm32l4/stm32l412", "l4"] 127 | l4x3 = ["stm32l4/stm32l4x3", "l4"] 128 | l4x5 = ["stm32l4/stm32l4x5", "l4"] 129 | l4x6 = ["stm32l4/stm32l4x6", "l4"] 130 | # todo: Handle l4+ (P, R, S, Q) 131 | 132 | # [L5](https://docs.rs/crate/stm32l5/latest/source/Cargo.toml) 133 | l552 = ["stm32l5/stm32l552", "l5"] 134 | l562 = ["stm32l5/stm32l562", "l5"] 135 | 136 | # [G0](https://docs.rs/crate/stm32g0/latest/source/Cargo.toml) 137 | g030 = ["stm32g0/stm32g030", "g0"] 138 | g031 = ["stm32g0/stm32g031", "g0"] 139 | g041 = ["stm32g0/stm32g041", "g0"] 140 | g050 = ["stm32g0/stm32g050", "g0"] 141 | g051 = ["stm32g0/stm32g051", "g0"] 142 | g061 = ["stm32g0/stm32g061", "g0"] 143 | g070 = ["stm32g0/stm32g070", "g0"] 144 | g071 = ["stm32g0/stm32g071", "g0"] 145 | g081 = ["stm32g0/stm32g081", "g0"] 146 | g0b0 = ["stm32g0/stm32g0b0", "g0"] 147 | g0b1 = ["stm32g0/stm32g0b1", "g0"] 148 | g0c1 = ["stm32g0/stm32g0c1", "g0"] 149 | 150 | # [G4](https://docs.rs/crate/stm32g4/latest/source/Cargo.toml) 151 | g431 = ["stm32g4/stm32g431", "g4"] 152 | g441 = ["stm32g4/stm32g441", "g4"] 153 | g471 = ["stm32g4/stm32g471", "g4"] 154 | g473 = ["stm32g4/stm32g473", "g4"] 155 | g474 = ["stm32g4/stm32g474", "g4"] 156 | g483 = ["stm32g4/stm32g483", "g4"] 157 | g484 = ["stm32g4/stm32g484", "g4"] 158 | g491 = ["stm32g4/stm32g491", "g4"] 159 | g4a1 = ["stm32g4/stm32g4a1", "g4"] 160 | 161 | h503 = ["stm32h5/stm32h503", "h5"] 162 | h562 = ["stm32h5/stm32h562", "h5"] 163 | h563 = ["stm32h5/stm32h563", "h5"] 164 | h573 = ["stm32h5/stm32h573", "h5"] 165 | 166 | # [H7](https://docs.rs/crate/stm32h7/latest/source/Cargo.toml) 167 | h735 = ["stm32h7/stm32h735", "h7"] 168 | h743 = ["stm32h7/stm32h743", "h7"] 169 | h743v = ["stm32h7/stm32h743v", "h7"] 170 | h747cm4 = ["stm32h7/stm32h747cm4", "h7"] 171 | h747cm7 = ["stm32h7/stm32h747cm7", "h7"] 172 | h753 = ["stm32h7/stm32h753", "h7"] 173 | h753v = ["stm32h7/stm32h753v", "h7"] 174 | h7b3 = ["stm32h7/stm32h7b3", "h7"] 175 | 176 | # [WB](https://docs.rs/crate/stm32wb/latest/source/Cargo.toml) 177 | wb55 = ["stm32wb/stm32wb55", "wb"] 178 | 179 | # [WL](https://docs.rs/crate/stm32wl/latest/source/Cargo.toml) 180 | wle5 = ["stm32wl/stm32wle5", "wl"] 181 | 182 | # [C0](https://docs.rs/crate/stm32c0/latest/source/Cargo.toml) 183 | c011 = ["stm32c0/stm32c011", "c0"] 184 | c031 = ["stm32c0/stm32c031", "c0"] 185 | c071 = ["stm32c0/stm32c071", "c0"] 186 | 187 | 188 | usb = ["dep:stm32-usbd"] 189 | #usb = ["dep:stm32-usbd", "usb-device", "usbd-serial"] 190 | 191 | usbotg_fs = ["synopsys-usb-otg/fs"] # eg F4 for FS. 192 | usbotg_hs = ["synopsys-usb-otg/hs"] # eg H7 for HS. 193 | 194 | can_bx = ["dep:bxcan"] 195 | can_fd_g = ["fdcan/fdcan_g0_g4_l5"] 196 | can_fd_h = ["fdcan/fdcan_h7"] 197 | 198 | net = ["dep:smoltcp"] 199 | 200 | embedded_hal = ["dep:embedded-hal", "dep:embedded-io"] 201 | monotonic = ["dep:rtic-monotonic"] 202 | 203 | defmt = ["dep:defmt"] 204 | 205 | # These features are used to featured gate sections of code that apply 206 | # to an entire family. 207 | f = [] 208 | l = [] 209 | c_g0 = [] # todo: A/R 210 | f3 = ["f"] 211 | f4 = ["f"] 212 | l4 = ["l"] 213 | l5 = ["l"] 214 | g0 = ["c_g0"] 215 | g4 = [] 216 | h5 = [] 217 | h7 = [] 218 | wb = [] 219 | wl = [] 220 | c0 = ["c_g0"] -------------------------------------------------------------------------------- /src/usb_otg.rs: -------------------------------------------------------------------------------- 1 | //! USB support, including for simulated COM ports. This module is a thin wrapper required to work with 2 | //! the `usbd` crate. 3 | //! 4 | //! Requires the `usbotg_fs` or `usbotg_hs` features. 5 | //! Used on F4, L4x6, and H7. Others use the `usb` module. 6 | 7 | // Based on `stm3h7xx-hal` 8 | 9 | #[cfg(all(feature = "h7", feature = "usbotg_fs"))] 10 | compile_error!("target only supports usbotg_hs feature"); 11 | #[cfg(all(not(feature = "h7"), feature = "usbotg_hs"))] 12 | compile_error!("target only supports usbotg_fs feature"); 13 | 14 | use cfg_if::cfg_if; 15 | pub use synopsys_usb_otg::UsbBus; 16 | use synopsys_usb_otg::UsbPeripheral; 17 | 18 | use crate::{ 19 | gpio::Pin, 20 | pac::{self, PWR, RCC}, 21 | }; 22 | 23 | cfg_if! { 24 | if #[cfg(feature = "usbotg_hs")] { 25 | // On older H7s (H743 etc), OTG1 maps to pisn PB14 and PB15. 26 | type Usb1GlobalRegType = pac::OTG1_HS_GLOBAL; 27 | type Usb1DeviceRegType = pac::OTG1_HS_DEVICE; 28 | type Usb1PwrclkRegType = pac::OTG1_HS_PWRCLK; 29 | 30 | cfg_if!{ 31 | // Note that on STM32H743 and related MCUs, OTG2 is known as "USB-FS", which 32 | // can be a bit confusing. This refers to the USB periphral on pins PA11 and PA12 33 | // for those MCUs. On newer ones like H723, use OTG1, which in that case, still 34 | // maps to PA11 and PA12. 35 | if #[cfg(not(any(feature = "h735", feature = "h7b3")))] { 36 | type Usb2RegGlobalType = pac::OTG2_HS_GLOBAL; 37 | type Usb2RegDeviceType = pac::OTG2_HS_DEVICE; 38 | type Usb2RegPwrclkType = pac::OTG2_HS_PWRCLK; 39 | } 40 | } 41 | } else if #[cfg(feature = "usbotg_fs")] { 42 | // Eg F4 and L4x6. 43 | type Usb1GlobalRegType = pac::OTG_FS_GLOBAL; 44 | type Usb1DeviceRegType = pac::OTG_FS_DEVICE; 45 | type Usb1PwrclkRegType = pac::OTG_FS_PWRCLK; 46 | } 47 | } 48 | 49 | pub struct Usb1 { 50 | pub usb_global: Usb1GlobalRegType, 51 | pub usb_device: Usb1DeviceRegType, 52 | pub usb_pwrclk: Usb1PwrclkRegType, 53 | pub hclk: u32, 54 | } 55 | 56 | impl Usb1 { 57 | pub fn new( 58 | usb_global: Usb1GlobalRegType, 59 | usb_device: Usb1DeviceRegType, 60 | usb_pwrclk: Usb1PwrclkRegType, 61 | hclk: u32, 62 | ) -> Self { 63 | Self { 64 | usb_global, 65 | usb_device, 66 | usb_pwrclk, 67 | hclk, 68 | } 69 | } 70 | } 71 | 72 | cfg_if! { 73 | if #[cfg(all(feature = "h7", not(any(feature = "h735", feature = "h7b3"))))] { 74 | pub struct Usb2 { 75 | pub usb_global: Usb2RegGlobalType, 76 | pub usb_device: Usb2RegDeviceType, 77 | pub usb_pwrclk: Usb2RegPwrclkType, 78 | pub hclk: u32, 79 | } 80 | 81 | impl Usb2 { 82 | pub fn new( 83 | usb_global: Usb2RegGlobalType, 84 | usb_device: Usb2RegDeviceType, 85 | usb_pwrclk: Usb2RegPwrclkType, 86 | hclk: u32, 87 | ) -> Self { 88 | Self { 89 | usb_global, 90 | usb_device, 91 | usb_pwrclk, 92 | hclk, 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | macro_rules! usb_peripheral { 100 | ($USB:ident, $GLOBAL:ident, $clock_enable_reg:ident, $reset_reg:ident, $en:ident, $rst:ident) => { 101 | unsafe impl Sync for $USB {} 102 | 103 | unsafe impl UsbPeripheral for $USB { 104 | const REGISTERS: *const () = $GLOBAL::ptr() as *const (); 105 | 106 | #[cfg(feature = "usbotg_fs")] 107 | const HIGH_SPEED: bool = false; 108 | #[cfg(feature = "usbotg_hs")] 109 | const HIGH_SPEED: bool = true; 110 | 111 | const FIFO_DEPTH_WORDS: usize = 1024; // <-- do something here maybe? 112 | const ENDPOINT_COUNT: usize = 9; // <-- 113 | 114 | fn enable() { 115 | let pwr = unsafe { &*PWR::ptr() }; 116 | let rcc = unsafe { &*RCC::ptr() }; 117 | 118 | // USB Regulator in BYPASS mode 119 | #[cfg(feature = "h7")] 120 | // pwr.cr3().modify(|_, w| w.usbregen().bit(true)); // todo ? 121 | pwr.cr3().modify(|_, w| w.usb33den().bit(true)); 122 | #[cfg(feature = "l4x6")] // this was present in the usb module 123 | pwr.cr2().modify(|_, w| w.usv().bit(true)); 124 | // The f4 doesn't seem to have anything similar 125 | 126 | // Enable USB peripheral 127 | rcc.$clock_enable_reg().modify(|_, w| w.$en().bit(true)); 128 | 129 | // Reset USB peripheral 130 | rcc.$reset_reg().modify(|_, w| w.$rst().bit(true)); 131 | rcc.$reset_reg().modify(|_, w| w.$rst().clear_bit()); 132 | } 133 | 134 | fn ahb_frequency_hz(&self) -> u32 { 135 | // For correct operation, the AHB frequency should be higher 136 | // than 30MHz. See RM0433 Rev 7. Section 57.4.4. This is checked 137 | // by the UsbBus implementation in synopsys-usb-otg. 138 | 139 | self.hclk 140 | } 141 | } 142 | }; 143 | } 144 | 145 | cfg_if! { 146 | if #[cfg(any(feature = "f4", feature = "l4"))] { 147 | usb_peripheral! { 148 | Usb1, Usb1GlobalRegType, ahb2enr, ahb2rstr, otgfsen, otgfsrst 149 | } 150 | } else if #[cfg(feature = "h7")] { 151 | usb_peripheral! { 152 | Usb1, Usb1GlobalRegType, ahb1enr, ahb1rstr, usb1otgen, usb1otgrst 153 | } 154 | } 155 | } 156 | 157 | pub type Usb1BusType = UsbBus; 158 | 159 | cfg_if! { 160 | if #[cfg(all(feature = "h7", not(any(feature = "h735", feature = "h7b3"))))] { 161 | usb_peripheral! { 162 | Usb2, Usb2RegGlobalType, ahb1enr, ahb1rstr, usb2otgen, usb2otgrst 163 | } 164 | 165 | pub type Usb2BusType = UsbBus; 166 | } 167 | } 168 | 169 | cfg_if! { 170 | if #[cfg(feature = "h7")] { 171 | pub struct Usb1Ulpi { 172 | pub usb_global: Usb1GlobalRegType, 173 | pub usb_device: Usb1DeviceRegType, 174 | pub usb_pwrclk: Usb1PwrclkRegType, 175 | pub prec: u32, // todo: What should this be? Maybe d2ccip2 / cdccip2 ? 176 | pub hclk: u32, 177 | pub ulpi_clk: Pin, 178 | pub ulpi_dir: Pin, 179 | pub ulpi_nxt: Pin, 180 | pub ulpi_stp: Pin, 181 | pub ulpi_d0: Pin, 182 | pub ulpi_d1: Pin, 183 | pub ulpi_d2: Pin, 184 | pub ulpi_d3: Pin, 185 | pub ulpi_d4: Pin, 186 | pub ulpi_d5: Pin, 187 | pub ulpi_d6: Pin, 188 | pub ulpi_d7: Pin, 189 | } 190 | 191 | unsafe impl Sync for Usb1Ulpi {} 192 | 193 | 194 | unsafe impl UsbPeripheral for Usb1Ulpi { 195 | const REGISTERS: *const () = Usb1GlobalRegType::ptr() as *const (); 196 | 197 | const HIGH_SPEED: bool = true; 198 | const FIFO_DEPTH_WORDS: usize = 1024; 199 | const ENDPOINT_COUNT: usize = 9; 200 | 201 | fn enable() { 202 | let rcc = unsafe { &*pac::RCC::ptr() }; 203 | 204 | // Enable USB peripheral 205 | rcc.ahb1enr().modify(|_, w| w.usb1otgen().enabled()); 206 | 207 | // Enable ULPI Clock 208 | rcc.ahb1enr().modify(|_, w| w.usb1otgulpien().enabled()); 209 | 210 | // Reset USB peripheral 211 | rcc.ahb1rstr().modify(|_, w| w.usb1otgrst().bit(true)); 212 | rcc.ahb1rstr().modify(|_, w| w.usb1otgrst().clear_bit()); 213 | } 214 | 215 | fn ahb_frequency_hz(&self) -> u32 { 216 | self.hclk 217 | } 218 | 219 | fn phy_type(&self) -> synopsys_usb_otg::PhyType { 220 | synopsys_usb_otg::PhyType::ExternalHighSpeed 221 | } 222 | } 223 | pub type Usb1UlpiBusType = UsbBus; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /examples/gpio.rs: -------------------------------------------------------------------------------- 1 | //! This module includes an overview of GPIO features available. 2 | //! Most functionality is included as methods to the 3 | //! [gpio::Pin struct](https://docs.rs/stm32-hal2/latest/stm32_hal2/gpio/struct.Pin.html). 4 | //! 5 | //! For project structure and debugging boilerplate, see the `synax_overview` example. 6 | 7 | #![no_main] 8 | #![no_std] 9 | 10 | use cortex_m::delay::Delay; 11 | use cortex_m_rt::entry; 12 | use critical_section::with; 13 | use embedded_hal::digital::OutputPin; 14 | use hal::{ 15 | adc::{Adc, AdcChannel, Align, CkMode, InputType, OperationMode}, 16 | clocks::Clocks, 17 | gpio::{self, Edge, OutputSpeed, Pin, PinMode, PinState, Port}, 18 | low_power, pac, 19 | prelude::*, 20 | }; 21 | 22 | // Set up an output pin in a globally-accessible mutex. This is useful for accessing 23 | // peripherals in interrupt contexts. We use a macro imported in the 24 | // `prelude` module to simplify this syntax, and accessing it later. 25 | // Arguments are a list of (global name to store as, type) tuples. 26 | // This macro is imported in the prelude. 27 | make_globals!((EXAMPLE_OUTPUT, Pin), (DEBOUNCE_TIMER, Timer),); 28 | 29 | /// This function includes type signature examples using `GpioPin`s from this library, 30 | /// and generic ones that implemented `embedded-hal` traits. 31 | fn example_type_sigs(pin1: &mut O, pin2: &mut Pin) { 32 | let setting = pin2.is_high(); 33 | 34 | // If using `embedded-hal` traits, you need to append `.unwrap()`, or `.ok()`, since these 35 | // traits are fallible, even though our stm32 implementation is not. 36 | pin1.set_low().ok(); 37 | } 38 | 39 | /// An example function to set up the pins that don't need to be interacted with directly later. 40 | /// For example, ones used with buses (eg I2C, SPI, UART), USB, ADC, and DAC pins. 41 | /// This may also include input pins that trigger interrupts, and aren't polled. 42 | pub fn setup_pins() { 43 | // Set up I2C pins 44 | let mut scl = Pin::new(Port::B, 6, PinMode::Alt(4)); 45 | scl.output_type(OutputType::OpenDrain); 46 | 47 | let mut sda = Pin::new(Port::B, 7, PinMode::Alt(4)); 48 | sda.output_type(OutputType::OpenDrain); 49 | 50 | // Set up SPI pins 51 | let _sck = Pin::new(Port::A, 5, PinMode::Alt(5)); 52 | let _miso = Pin::new(Port::A, 6, PinMode::Alt(5)); 53 | let _mosi = Pin::new(Port::A, 7, PinMode::Alt(5)); 54 | 55 | // Set up UART pins 56 | let _uart_tx = Pin::new(Port::A, 9, PinMode::Alt(7)); 57 | let _uart_rx = Pin::new(Port::A, 10, PinMode::Alt(7)); 58 | 59 | // Set up USB pins 60 | let _usb_dm = Pin::new(Port::A, 11, PinMode::Alt(14)); 61 | let _usb_dp = Pin::new(Port::A, 12, PinMode::Alt(14)); 62 | 63 | // Set the ADC pin to analog mode, to prevent parasitic power use. 64 | let _adc_pin = Pin::new(Port::B, 0, PinMode::Analog); 65 | 66 | // Set DAC pin to analog mode, to prevent parasitic power use. 67 | let _dac_pin = Pin::new(Port::A, 4, PinMode::Analog); 68 | 69 | // Set up PWM. // Timer 2, channel 1. 70 | let _pwm_pin = Pin::new(Port::A, 0, PinMode::Alt(1)); 71 | 72 | // Set up buttons, with pull-up resistors that trigger on the falling edge. 73 | let mut up_btn = Pin::new(Port::B, 3, PinMode::Input); 74 | up_btn.pull(Pull::Up); 75 | up_btn.enable_interrupt(Edge::Falling); 76 | 77 | let mut dn_btn = Pin::new(Port::A, 4, PinMode::Input); 78 | dn_btn.pull(Pull::Up); 79 | dn_btn.enable_interrupt(Edge::Falling); 80 | } 81 | 82 | /// Initialize peripherals; run this once at startup. Move to a dedicated module as required. 83 | fn init() { 84 | // Set up CPU peripherals 85 | let mut cp = cortex_m::Peripherals::take().unwrap(); 86 | // Set up microcontroller peripherals 87 | let mut dp = pac::Peripherals::take().unwrap(); 88 | 89 | let clock_cfg = Clocks::default(); 90 | 91 | if clock_cfg.setup().is_err() { 92 | defmt::error!("Unable to configure clocks due to a speed error.") 93 | }; 94 | 95 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 96 | 97 | // Call a function we've made to help organize our pin setup code. 98 | setup_pins(); 99 | 100 | // Example pins PB5 and PB6. 101 | let mut example_output = Pin::new(Port::B, 5, PinMode::Output); 102 | let mut example_input = Pin::new(Port::B, 6, PinMode::Input); 103 | 104 | // Set the output speed. 105 | example_output.output_speed(OutputSpeed::Medium); 106 | 107 | // A simple button debounce: Use a timer with a period between the maximum bouncing 108 | // time you expect, and the minimum time bewteen actuations. In this time, we've chosen 5Hz, 109 | // or 200ms. Note that there are other approaches as well. 110 | let mut debounce_timer = Timer::new_tim15(dp.TIM15, 5., &clock_cfg); 111 | debounce_timer.enable_interrupt(TimerInterrupt::Update); 112 | 113 | example_type_sigs(&mut example_output, &mut example_input); 114 | 115 | let state = example_input.get_state(); // eg `PinState::High` or `PinState::Low` 116 | let state2 = example_input.is_high(); // eg `true` or `false`. 117 | 118 | // Set high. 119 | example_output.set_state(PinState::High); 120 | 121 | // Simpler syntax to set state, and use with a delay, provided by the Cortex-M systick. 122 | example_output.set_high(); 123 | delay.delay_ms(500); 124 | example_output.set_low(); 125 | 126 | // Make the debounce timer global, so we can access it in interrupt contexts. 127 | init_globals!( 128 | (EXAMPLE_OUTPUT, example_output), 129 | (DEBOUNCE_TIMER, debounce_timer) 130 | ); 131 | 132 | // Unmask interrupt lines, and set priority. Lower values are higher priority. 133 | setup_nvic!([(EXTI3, 3), (EXTI4, 2), (TIM15, 3),], cp); 134 | } 135 | 136 | #[entry] 137 | fn main() -> ! { 138 | init(); 139 | 140 | loop { 141 | low_power::sleep_now(); 142 | } 143 | } 144 | 145 | #[interrupt] 146 | /// Interrupt handler for PB3. This ISR is called when this push button goes low. 147 | fn EXTI3() { 148 | with(|cs| { 149 | // Clear the interrupt flag, to prevent continous firing. 150 | gpio::clear_exti_interrupt(3); 151 | 152 | // A helper macro to access the pin and timer we stored in mutexes. 153 | access_global!(DEBOUNCE_TIMER, debounce_timer, cs); 154 | if debounce_timer.is_enabled() { 155 | return; 156 | } 157 | 158 | access_global!(EXAMPLE_OUTPUT, example_output, cs); 159 | 160 | // Set a pin high; 161 | example_output.set_high(); 162 | 163 | debounce_timer.enable(); 164 | }); 165 | } 166 | 167 | #[interrupt] 168 | /// Interrupt handler for PA4. This ISR is called when this push button goes low. 169 | fn EXTI4() { 170 | with(|cs| { 171 | // with(|cs| { 172 | // Clear the interrupt flag, to prevent continous firing. 173 | gpio::clear_exti_interrupt(4); 174 | 175 | access_global!(DEBOUNCE_TIMER, debounce_timer, cs); 176 | if debounce_timer.is_enabled() { 177 | return; 178 | } 179 | 180 | // This accomplishes the same as `access_global!`, and demonstrates 181 | // what that macro does. 182 | let mut p = EXAMPLE_OUTPUT.borrow(cs).borrow_mut(); 183 | let mut example_output = p.as_mut().unwrap(); 184 | 185 | example_output.set_low(); 186 | 187 | debounce_timer.enable(); 188 | }); 189 | } 190 | 191 | #[interrupt] 192 | /// We use tim15 for button debounce. 193 | fn TIM15() { 194 | // Or, to clear interrupt without a CS: timer::clear_update_interrupt(15); 195 | with(|cs| { 196 | access_global!(DEBOUNCE_TIMER, debounce_timer, cs); 197 | // Clear the interrupt flag. If you ommit this, it will fire repeatedly. 198 | debounce_timer.clear_interrupt(TimerInterrupt::Update); 199 | 200 | // Disable the timer until next time you press a button. 201 | debounce_timer.disable(); 202 | }); 203 | } 204 | 205 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 206 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 207 | #[defmt::panic_handler] 208 | fn panic() -> ! { 209 | cortex_m::asm::udf() 210 | } 211 | -------------------------------------------------------------------------------- /src/can/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for Controller Area Network (CAN) bus. Thinly wraps the [bxCAN](https://docs.rs/bxcan/0.5.0/bxcan/) 2 | //! or [can-fd](https://crates.io/keywords/can-fd) libraries. 3 | //! 4 | //! Requires the `can_bx` or `can_fd_g[h]` features. F3, F4, and L4 use BX CAN. G0, G4, L5, and H7 use FD CAN. 5 | 6 | use cfg_if::cfg_if; 7 | 8 | use crate::{ 9 | error::{Error, Result}, 10 | pac::RCC, 11 | util::{bounded_loop, rcc_en_reset}, 12 | }; 13 | 14 | // todo: H5 support. 15 | cfg_if! { 16 | if #[cfg(feature = "f3")] { 17 | use bxcan; 18 | use crate::pac::{can, CAN}; 19 | 20 | } else if #[cfg(any(feature = "f4", feature = "l4"))] { 21 | use bxcan; 22 | // todo: F4 has CAN2 as well. 23 | use crate::pac::{CAN1 as CAN}; 24 | } else { // eg G0, H7 25 | use fdcan; 26 | // todo: CAN2 on H7. 27 | use crate::pac::{FDCAN1 as CAN}; 28 | } 29 | } 30 | 31 | cfg_if! { 32 | if #[cfg(feature = "g4")] { 33 | pub mod g4; 34 | pub use g4::*; 35 | } else { 36 | /// Interface to the CAN peripheral. 37 | pub struct Can { 38 | pub regs: CAN, 39 | } 40 | 41 | impl Can { 42 | /// Initialize a CAN peripheral, including enabling and resetting 43 | /// its RCC peripheral clock. This is not handled by the `bxcan` or `canfd` crates. 44 | pub fn new(regs: CAN) -> Self { 45 | let rcc = unsafe { &*RCC::ptr() }; 46 | 47 | cfg_if! { 48 | if #[cfg(feature = "f3")] { 49 | rcc_en_reset!(apb1, can, rcc); 50 | } else if #[cfg(any(feature = "f4", feature = "l4"))] { 51 | rcc_en_reset!(apb1, can1, rcc); 52 | } else if #[cfg(feature = "h7")]{ 53 | // We don't yet have apb1h support in `rcc_en_reset`. 54 | rcc.apb1henr().modify(|_, w| w.fdcanen().bit(true)); 55 | rcc.apb1hrstr().modify(|_, w| w.fdcanrst().bit(true)); 56 | rcc.apb1hrstr().modify(|_, w| w.fdcanrst().clear_bit()); 57 | 58 | // set_message_ram_layout(); 59 | 60 | } else { 61 | rcc_en_reset!(apb1, fdcan, rcc); 62 | } 63 | } 64 | 65 | Self { regs } 66 | } 67 | 68 | /// Print the (raw) contents of the status register. 69 | pub fn read_status(&self) -> u32 { 70 | cfg_if! { 71 | if #[cfg(any(feature = "h7", feature = "l5"))] { 72 | unsafe { self.regs.psr().read().bits() } 73 | } else { 74 | unsafe { self.regs.msr.read().bits() } 75 | } 76 | } 77 | } 78 | } 79 | 80 | #[cfg(feature = "h7")] 81 | // todo: Troubleshooting. COpied from H7xx-hal 82 | /// Set the message RAM layout. This is flexible on H7. This function hard-sets it to the setting 83 | /// that is hard-set by hardware on G4. 84 | /// todo: Allow flexibility. 85 | /// 86 | /// Note: Perhaps due to a reset of message ram called by the FDCAN crate's `.into_config_mode()`, 87 | /// we run this in application firmware once in config mode. Although a better API would be in the constructor 88 | /// This must be done after initial setup (Enabling RCC clocks most-likely). 89 | pub fn set_message_ram_layout() -> Result<()> { 90 | let regs = unsafe { &(*CAN::ptr()) }; 91 | 92 | // RM, section 56.4.1: Operation modes: "Access to the FDCAN configuration registers is only 93 | // enabled when both INIT bit in FDCAN_CCCR register and CCE bit in FDCAN_CCCR register are set. 94 | // Note: we do this as 2 separate writes. RM: "CCE bit in FDCAN_CCCR register can only be set/cleared while INIT bit in FDCAN_CCCR 95 | // is set. CCE bit in FDCAN_CCCR register is automatically cleared when INIT bit in 96 | // FDCAN_CCCR is cleared." 97 | regs.cccr().modify(|_, w| w.init().bit(true)); 98 | bounded_loop!(regs.cccr.read().init().bit_is_clear(), Error::RegisterUnchanged); 99 | regs.cccr().modify(|_, w| w.cce().bit(true)); 100 | bounded_loop!(regs.cccr.read().cce().bit_is_clear(), Error::RegisterUnchanged); 101 | 102 | let mut word_addr = 0x000; // todo: 0x400 for FDCAN2? 103 | 104 | use fdcan::message_ram::*; 105 | 106 | // 11-bit filter 107 | regs.sidfc() 108 | .modify(|_, w| unsafe { w.flssa().bits(word_addr) }); 109 | word_addr += STANDARD_FILTER_MAX as u16; 110 | 111 | // 29-bit filter 112 | regs.xidfc() 113 | .modify(|_, w| unsafe { w.flesa().bits(word_addr) }); 114 | word_addr += 2 * EXTENDED_FILTER_MAX as u16; 115 | 116 | // Rx FIFO 0 117 | regs.rxf0c().modify(|_, w| unsafe { 118 | w.f0sa() 119 | .bits(word_addr) 120 | .f0s() 121 | .bits(RX_FIFO_MAX) 122 | .f0wm() 123 | .bits(RX_FIFO_MAX) 124 | }); 125 | word_addr += 18 * RX_FIFO_MAX as u16; 126 | 127 | // Rx FIFO 1 128 | regs.rxf1c().modify(|_, w| unsafe { 129 | w.f1sa() 130 | .bits(word_addr) 131 | .f1s() 132 | .bits(RX_FIFO_MAX) 133 | .f1wm() 134 | .bits(RX_FIFO_MAX) 135 | }); 136 | word_addr += 18 * RX_FIFO_MAX as u16; 137 | 138 | // Rx buffer - see below 139 | // Tx event FIFO 140 | regs.txefc().modify(|_, w| unsafe { 141 | w.efsa() 142 | .bits(word_addr) 143 | .efs() 144 | .bits(TX_EVENT_MAX) 145 | .efwm() 146 | .bits(TX_EVENT_MAX) 147 | }); 148 | word_addr += 2 * TX_EVENT_MAX as u16; 149 | 150 | // Tx buffers 151 | regs.txbc() 152 | .modify(|_, w| unsafe { w.tbsa().bits(word_addr).tfqs().bits(TX_FIFO_MAX) }); 153 | word_addr += 18 * TX_FIFO_MAX as u16; 154 | 155 | // Rx Buffer - not used 156 | regs.rxbc().modify(|_, w| unsafe { w.rbsa().bits(word_addr) }); 157 | 158 | // TX event FIFO? 159 | // Trigger memory? 160 | 161 | // Set the element sizes to 16 bytes 162 | regs.rxesc() 163 | .modify(|_, w| unsafe { w.rbds().bits(0b111).f1ds().bits(0b111).f0ds().bits(0b111) }); 164 | regs.txesc().modify(|_, w| unsafe { w.tbds().bits(0b111) }); 165 | } 166 | 167 | // Implement the traits required for the `bxcan` or `fdcan` library. 168 | cfg_if! { 169 | if #[cfg(feature = "can_bx")] { 170 | unsafe impl bxcan::Instance for Can { 171 | const REGISTERS: *mut bxcan::RegisterBlock = CAN::ptr() as *mut _; 172 | } 173 | 174 | unsafe impl bxcan::FilterOwner for Can { 175 | #[cfg(any(feature = "f3", feature = "f4"))] 176 | const NUM_FILTER_BANKS: u8 = 28; 177 | #[cfg(any(feature = "l4"))] 178 | const NUM_FILTER_BANKS: u8 = 14; 179 | } 180 | 181 | unsafe impl bxcan::MasterInstance for Can {} 182 | } else { 183 | unsafe impl fdcan::Instance for Can { 184 | const REGISTERS: *mut fdcan::RegisterBlock = CAN::ptr() as *mut _; 185 | } 186 | 187 | unsafe impl fdcan::message_ram::Instance for Can { 188 | #[cfg(feature = "h7")] 189 | // H743 RM, table 8. "Register boundary addresses". 0x4000_AC00 - 0x4000_D3FF". CAN message RAM. 190 | const MSG_RAM: *mut fdcan::message_ram::RegisterBlock = (0x4000_ac00 as *mut _); 191 | // todo: (0x4000_ac00 + 0x1000) for H7, CAN2. 192 | // todo: (0x4000_a750 as *mut _) for G4, CAN2 193 | // todo: (0x4000_aaa0 as *mut _) fir G4 CAN3. 194 | } 195 | } 196 | } 197 | // todo: H5 support. 198 | 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /examples/syntax_overview/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This example shows a complete project, including file structure, and config 2 | //! needed to flash using an ST-Link. The project structure is based on 3 | //! [Knurling's app-template](https://github.com/knurling-rs/app-template). 4 | //! This file demonstrates an overview of this library's features. 5 | 6 | #![no_main] 7 | #![no_std] 8 | 9 | use core::sync::atomic::{AtomicUsize, Ordering}; 10 | 11 | use cortex_m::{ 12 | self, 13 | delay::Delay, 14 | peripheral::NVIC, 15 | }; 16 | use critical_section::with; 17 | use cortex_m_rt::entry; 18 | 19 | // These lines are part of our setup for debug printing. 20 | use defmt_rtt as _; 21 | use panic_probe as _; 22 | 23 | // Import parts of this library we use. You could use this style, or perhaps import 24 | // less here. 25 | use hal::{ 26 | self, 27 | adc::{self, Adc, AdcChannel}, 28 | clocks::Clocks, 29 | dac::{Dac, DacChannel, DacBits}, 30 | dma::{Dma, DmaPeriph, DmaChannel, DmaInput, DmaInterrupt, DmaReadBuf, DmaWriteBuf}, 31 | flash::Flash, 32 | gpio::{Edge, Pin, Port, PinMode, OutputType, Pull}, 33 | i2c::I2c, 34 | low_power, 35 | pac, 36 | rtc::{Rtc, RtcClockSource, RtcConfig}, 37 | usart::{Usart, UsartInterrupt, UsartConfig}, 38 | spi::{self, BaudRate, Spi, SpiConfig, SpiMode}, 39 | timer::{Timer, TimerInterrupt}, 40 | }; 41 | 42 | #[entry] 43 | fn main() -> ! { 44 | // Set up ARM Cortex-M peripherals. These are common to many MCUs, including all STM32 ones. 45 | let mut cp = cortex_m::Peripherals::take().unwrap(); 46 | // Set up peripherals specific to the microcontroller you're using. 47 | let mut dp = pac::Peripherals::take().unwrap(); 48 | 49 | // This line is required to prevent the debugger from disconnecting on entering WFI. 50 | // This appears to be a limitation of many STM32 families. Not required in production code, 51 | // and significantly increases power consumption in low-power modes. 52 | hal::debug_workaround(); 53 | 54 | // Create an initial clock configuration that uses the MCU's internal oscillator (HSI), 55 | // sets the MCU to its maximum system clock speed. 56 | let clock_cfg = Clocks::default(); 57 | 58 | // Write the clock configuration to the MCU. If you wish, you can modify `clocks` above 59 | // in accordance with [its docs](https://docs.rs/stm32-hal2/0.2.0/stm32_hal2/clocks/index.html), 60 | // and the `clock_cfg` example. 61 | clock_cfg.setup().unwrap(); 62 | 63 | // Setup a delay, based on the Cortex-m systick. 64 | let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); 65 | 66 | delay.delay_ms(500); 67 | 68 | // Set up the realtime clock. This is useful for keeping track of dates and times, or 69 | // setting a 'timer', especially for long durations. Can be used to wake up the MCU 70 | // from most low-power modes. 71 | let mut rtc = Rtc::new( 72 | dp.RTC, 73 | RtcConfig { 74 | clock_source: RtcClockSource::Lse, 75 | ..Default::default() 76 | } 77 | ); 78 | 79 | // Read from and write to the onboard flash memory. 80 | let mut flash = Flash::new(dp.FLASH); 81 | 82 | // Make sure to select a page farther than the farthest page this program uses! 83 | let flash_page = 20; 84 | flash.erase_page(flash_page).ok(); 85 | // Write a byte array to the flash. 86 | flash.write_page(10, &[1, 2, 3]).ok(); 87 | 88 | let flash_contents = flash.read(10, 0); 89 | 90 | // An example GPIO pin, configured in output mode. 91 | let mut pa15 = Pin::new(Port::A, 15, PinMode::Output); 92 | pa15.set_high(); 93 | 94 | pa15.enable_interrupt(Edge::Rising); 95 | 96 | // Configure pins for I2c. 97 | let mut scl = Pin::new(Port::B, 6, PinMode::Alt(4)); 98 | scl.output_type(OutputType::OpenDrain); 99 | 100 | let mut sda = Pin::new(Port::B, 7, PinMode::Alt(4)); 101 | sda.output_type(OutputType::OpenDrain); 102 | 103 | // Set up an I2C peripheral, running at 100Khz. 104 | let i2c = I2c::new(dp.I2C1, Default::default(), &clock_cfg); 105 | 106 | // Configure pins for I2c. 107 | let _sck = Pin::new(Port::A, 5, PinMode::Alt(5)); 108 | let _miso = Pin::new(Port::A, 6, PinMode::Alt(5)); 109 | let _mosi = Pin::new(Port::A, 7, PinMode::Alt(5)); 110 | 111 | // Configure DMA, to be used by peripherals. 112 | let mut dma = Dma::new(&mut dp.DMA1); 113 | 114 | dma::mux(DmaPeriph::Dma1, DmaChannel::C2, DmaInput::Adc2); 115 | 116 | let spi_cfg = SpiConfig { 117 | mode: SpiMode::mode3(), // SpiConfig::default() uses mode 0. 118 | ..Default::default() 119 | }; 120 | 121 | // Set up an SPI peripheral, running at 4Mhz, in SPI mode 0. 122 | let spi = Spi::new( 123 | dp.SPI1, 124 | spi_cfg, 125 | BadRate::Div32, // Eg 80Mhz apb clock / 32 = 2.5Mhz SPI clock. 126 | ); 127 | 128 | // Configure pins for UART. 129 | let _uart_tx = Pin::new(Port::A, 9, PinMode::Alt(7)); 130 | let _uart_rx = Pin::new(Port::A, 10, PinMode::Alt(7)); 131 | 132 | // Set up a UART peripheral. 133 | // Setup UART for connecting to the host 134 | let mut uart = Usart::new( 135 | dp.USART1, 136 | 9_600, 137 | UsartConfig::default(), 138 | &clock_cfg, 139 | ); 140 | 141 | // Or, to set a custom USART config: 142 | let usart_cfg = UsartConfig { 143 | parity: Parity::EnabledOdd, 144 | stop_bits: StopBits::S2, 145 | ..Default::default() 146 | }; 147 | let mut uart = Usart::new( 148 | dp.USART1, 149 | 9_600, 150 | usart_cfg, 151 | &clock_cfg, 152 | ); 153 | 154 | // Write a byte array to the UART 155 | uart.write(&[1, 2, 3, 4]); 156 | 157 | // Read a byte array from the UART. 158 | let buffer = [0_u8; 10]; 159 | uart.read(&mut uart_buffer, &mut dma); 160 | 161 | // Or, read and write using DMA: 162 | uart.write_dma(&[1, 2, 3, 4], DmaChannel::C2, Default::default(), DmaPeriph::Dma1); 163 | uart.read_dma(&mut uart_buffer, DmaChannel::C3, Default::default(), DmaPeriph::Dma1); 164 | 165 | // Set up the Analog-to-digital converter 166 | let _adc_pin = Pin::new(Port::B, 5, PinMode::Analog); 167 | 168 | let mut adc = Adc::new_adc1( 169 | dp.ADC1, 170 | Default::default(), 171 | clock_cfg.systick(), 172 | ); 173 | 174 | // Take a reading from ADC channel 1. 175 | let reading: u16 = adc.read(1); 176 | 177 | // Set up the Digital-to-analog converter 178 | let mut _dac_pin = Pin::new(Port::A, 12, PinMode::Analog); 179 | let mut dac = Dac::new(dp.DAC1, Default::default(), 3.3); 180 | dac.enable(DacChannel::C1); 181 | 182 | dac.write(DacChannel::C1, 2_048); // Set DAC output voltage to half VCC, eg 1.65V 183 | 184 | // Set up and start a timer; set it to fire interrupts at 5Hz. 185 | let mut timer = Timer::new_tim1(dp.TIM1, 0.2, Default::default(), &clock_cfg); 186 | timer.enable_interrupt(TimerInterrupt::Update); // Enable update event interrupts. 187 | timer.enable(); 188 | 189 | // You can read most peripheral status registers with a `read_status()` method: (Returns the 190 | // 32-bit status register contents). 191 | let status = timer.read_status(); 192 | 193 | // For pins that aren't called directly (Like the ones we set up for I2C, SPI, UART, ADC, and DAC), 194 | // consider a separate function: 195 | // fn setup_pins(gpioa: &mut GpioA, gpiob: &mut GpioB, exti: &mut EXTI, syscfg: &mut SYSCFG) { 196 | // let mut scl = gpiob.new_pin(6, PinMode::Alt(4)); 197 | // scl.output_type(OutputType::OpenDrain); 198 | // // ... 199 | // } 200 | 201 | loop { 202 | defmt::println!("Looping!"); // A print statement using DEFMT. 203 | // Enter a low power mode. The program will wake once an interrupt fires. 204 | // For example, the timer and GPIO interrupt above. But we haven't unmasked 205 | // their lines, so they won't work - see the `interrupts` example for that. 206 | low_power::sleep_now(); 207 | } 208 | } 209 | 210 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 211 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 212 | #[defmt::panic_handler] 213 | fn panic() -> ! { 214 | cortex_m::asm::udf() 215 | } 216 | 217 | /// Terminates the application and makes `probe-run` exit with exit-code = 0 218 | pub fn exit() -> ! { 219 | loop { 220 | cortex_m::asm::bkpt(); 221 | } 222 | } -------------------------------------------------------------------------------- /examples/conductivity_module/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This is the entry point for firmware for the 2 | //! [AnyLeaf conductivity](https://www.anyleaf.org/ec-module) module. 3 | //! Conductivity-measuring code is in `ec.rs`. 4 | 5 | #![no_main] 6 | #![no_std] 7 | #![allow(non_snake_case)] 8 | 9 | use core::{ 10 | cell::{Cell, RefCell}, 11 | sync::atomic::{AtomicUsize, Ordering}, 12 | }; 13 | 14 | use cortex_m::{ 15 | self, 16 | peripheral::NVIC, 17 | }; 18 | use critical_section::{with, Mutex}; 19 | 20 | use cortex_m_rt::entry; 21 | 22 | use hal::{ 23 | self, 24 | clocks::Clocks, 25 | dac::{Dac, DacBits, DacChannel}, 26 | gpio::{OutputType, Pin, PinMode, Port}, 27 | i2c::I2c, 28 | low_power, 29 | pac::{self, interrupt, I2C1, USART1}, 30 | prelude::*, 31 | timer::{CountDir, OutputCompare, TimChannel, Timer}, 32 | usart::{Usart, UsartConfig, UsartInterrupt}, 33 | }; 34 | 35 | use defmt_rtt as _; // global logger 36 | use panic_probe as _; 37 | 38 | mod ec; 39 | 40 | // Set up global mutable variables, as `Mutex>>`. We use a macro to simplify syntax. 41 | make_globals!( 42 | (I2C, I2c), 43 | (SENSOR, ec::EcSensor), 44 | (UART, Usart) 45 | ); 46 | 47 | static EXC_MODE: Mutex> = Mutex::new(Cell::new(ExcMode::ReadingOnly)); 48 | 49 | const MSG_SIZE: usize = 11; 50 | const SUCCESS_MSG: [u8; 3] = [50, 50, 50]; // Send this to indicate success. 51 | const ERROR_MSG: [u8; 3] = [99, 99, 99]; // Send this to indicate an error. 52 | 53 | // `OK_BIT` and `ERROR_BIT` are the preceding bit of each reading from the water monitor. 54 | // They indicate a sensor error, not a serial comms error. 55 | const OK_BIT: u8 = 10; 56 | // const ERROR_BIT: u8 = 20; 57 | const MSG_START_BYTES: [u8; 2] = [100, 150]; 58 | const MSG_END_BYTES: [u8; 1] = [200]; 59 | 60 | // See ADS1115 datasheet Section 9.6.3: Config Register. Start a differential conversion on channel 61 | // 0, with 0.512V full scale range, one-shot mode, no alert pin activity. 62 | const EC_CMD: u16 = 0b1100_1001_1000_0000; 63 | 64 | // This is the same as the EC command, but uses ADC channel 1. // todo: Higher voltage range? 65 | const T_CMD: u16 = 0b1101_0101_1000_0000; 66 | 67 | #[derive(Clone, Copy, PartialEq)] 68 | /// Apply excitation currently only during readings, or always. 69 | pub enum ExcMode { 70 | ReadingOnly, 71 | AlwaysOn, 72 | } 73 | 74 | /// Set up the pins that have structs that don't need to be accessed after. 75 | pub fn setup_pins() { 76 | // Set `dac_pin` to analog mode, to prevent parasitic power use. 77 | let _dac_pin = Pin::new(Port::A, 4, PinMode::Analog); 78 | let _pwm_pin = Pin::new(Port::A, 0, PinMode::Alt(1)); 79 | 80 | // Setup UART for connecting to the host 81 | let _uart_tx = Pin::new(Port::A, 9, PinMode::Alt(7)); 82 | let _uart_rx = Pin::new(Port::A, 10, PinMode::Alt(7)); 83 | 84 | // Set up I2C pins, for communicating with the external ADC. 85 | let mut scl = Pin::new(Port::B, 6, PinMode::Alt(4)); 86 | scl.output_type(OutputType::OpenDrain); 87 | 88 | let mut sda = Pin::new(Port::B, 7, PinMode::Alt(4)); 89 | sda.output_type(OutputType::OpenDrain); 90 | } 91 | 92 | #[entry] 93 | fn main() -> ! { 94 | // Set up CPU peripherals 95 | let mut cp = cortex_m::Peripherals::take().unwrap(); 96 | // Set up microcontroller peripherals 97 | let mut dp = pac::Peripherals::take().unwrap(); 98 | 99 | // Set up clocks 100 | let clock_cfg = Clocks::default(); 101 | clock_cfg.setup().unwrap(); 102 | 103 | // Set up pins with appropriate modes. 104 | setup_pins(); 105 | 106 | // Set up I2C for the TI ADS1115 ADC. 107 | let i2c = I2c::new(dp.I2C1, Default::default(), &clock_cfg); 108 | 109 | // todo: Once on new QFN MCU: Gain 0, 1, 2 -> PA6, PA7, PB0 110 | // Set up pins used to control the gain-resistor-selecting multiplexer. 111 | let gain0 = Pin::new(Port::B, 0, PinMode::Output); 112 | let gain1 = Pin::new(Port::B, 1, PinMode::Output); 113 | let gain2 = Pin::new(Port::B, 2, PinMode::Output); 114 | 115 | // todo: You could probably use the onboard ADC to reduce cost and hardware complexity. 116 | 117 | // todo: Precision voltage ref on VDDA and VSSA, to improve accuracy? 118 | // set up the DAC, to control voltage into the conductivity circuit. 119 | let dac = Dac::new(dp.DAC1, DacBits::TwelveR, 3.3); 120 | 121 | // `pwm_timer` is used to change polarity-switching rate of the excitation 122 | // current across the probe terminals, using an analog switch. 123 | let mut pwm_timer = Timer::new_tim2(dp.TIM2, 2_400., Default::default(), &clock_cfg); 124 | pwm_timer.set_auto_reload_preload(true); 125 | pwm_timer.enable_pwm_output(TimChannel::C1, OutputCompare::Pwm1, 0.5); 126 | pwm_timer.enable(); 127 | 128 | // Setup UART for connecting to the host 129 | let mut uart = Usart::new( 130 | dp.USART1, 131 | 9_600, 132 | UsartConfig::default(), 133 | &clock_cfg, 134 | ); 135 | 136 | // Trigger an interrupt if we receive our start character over UART. 137 | uart.enable_interrupt(UsartInterrupt::CharDetect(MSG_START_BYTES[0])); 138 | 139 | // Initialize to a 1.0 cell constant; will be changed by the user later with a command 140 | // if required. 141 | let sensor = ec::EcSensor::new(dac, pwm_timer, (gain0, gain1, gain2), 1.0); 142 | 143 | // Set up the global static variables so we can access them during interrupts. 144 | init_globals!( 145 | (I2C, i2c), 146 | (SENSOR, sensor), 147 | (UART, uart), 148 | ); 149 | 150 | setup_nvic!([(USART1, 2)], cp); 151 | 152 | loop { 153 | // Wait until we receive communication over UART; code to handle readings are handled in 154 | // the `USART1` ISR below. 155 | low_power::sleep_now(); 156 | } 157 | } 158 | 159 | #[interrupt] 160 | /// This Interrupt Service Routine (ISR) is triggered by UART activity. It determines 161 | /// what information is requested using a simple protocol, and adjusts setting, 162 | /// and sends readings over UART as required. 163 | fn USART1() { 164 | with(|cs| { 165 | access_global!(UART, uart, cs); 166 | uart.clear_interrupt(UsartInterrupt::CharDetect(0)); 167 | 168 | let mut msg = [0; MSG_SIZE]; 169 | 170 | // An alternative approach is to make a global MSG buffer, 171 | // and populate it with an interrupt at each byte recieved, 172 | // then process the start and/or end bits specially to trigger 173 | // this "msg_recieved" code. 174 | // for i in 0..MSG_SIZE { 175 | // // Wait for the next bit 176 | // while unsafe {(*pac::USART1::ptr()).isr().read().rxne().bit_is_clear()} {} 177 | // msg[i] = rx.read().unwrap_or(0); 178 | // } 179 | uart.read(&mut msg); 180 | 181 | // Bits 0:1 are start bits. Bit 2 identifies the command. Bits 3-9 182 | // can pass additional data to the command. Bit 10 is the end bit. 183 | 184 | if !(msg[0..2] == MSG_START_BYTES && msg[10] == MSG_END_BYTES[0]) { 185 | uart.write(&ERROR_MSG); 186 | return; 187 | } 188 | 189 | match msg[2] { 190 | 10 => { 191 | // Read conductivity 192 | access_global!(SENSOR, sensor, cs); 193 | access_global!(I2C, i2c, cs); 194 | 195 | // Convert the raw reading to a 16-bit integer. It will be the reading in µS/cm / K. 196 | sensor.dac.enable(DacChannel::C1); 197 | 198 | let reading = sensor.read(0x48, EC_CMD, i2c).unwrap_or(0.); 199 | 200 | // todo: Use integers all the way instead of ADC word -> ec float -> int? 201 | 202 | let reading_to_xmit = (reading * 1_000_000. / sensor.K_cell) as u16; 203 | 204 | if EXC_MODE.borrow(cs).get() == crate::ExcMode::ReadingOnly { 205 | sensor.dac.disable(DacChannel::C1); 206 | } 207 | 208 | // Split the u16 into 2 bytes. 209 | let r: [u8; 2] = reading_to_xmit.to_be_bytes(); 210 | uart.write(&[OK_BIT, r[0], r[1]]); 211 | } 212 | 213 | 11 => { 214 | // Read temperature 215 | access_global!(I2C, i2c, cs); 216 | 217 | let reading = ec::take_reading(0x48, T_CMD, i2c); 218 | 219 | // Split the i16 into 2 bytes. Send these bytes as-is from the ADC; 220 | // you'll need to decode with the reading software. 221 | let r: [u8; 2] = reading.to_be_bytes(); 222 | uart.write(&[OK_BIT, r[0], r[1]]); 223 | } 224 | 225 | 12 => { 226 | // Set excitation current mode 227 | EXC_MODE.borrow(cs).set(match msg[3] { 228 | 0 => ExcMode::ReadingOnly, 229 | 1 => ExcMode::AlwaysOn, 230 | _ => { 231 | uart.write(&ERROR_MSG); 232 | return; 233 | } 234 | }); 235 | 236 | uart.write(&SUCCESS_MSG); 237 | } 238 | 239 | 13 => { 240 | // Set cell constant, to accomodate different probes. 241 | access_global!(SENSOR, sensor, cs); 242 | sensor.K_cell = match msg[3] { 243 | 0 => 0.01, 244 | 1 => 0.1, 245 | 2 => 1., 246 | 3 => 10., 247 | _ => { 248 | uart.write(&ERROR_MSG); 249 | return; 250 | } 251 | }; 252 | 253 | uart.write(&SUCCESS_MSG); 254 | } 255 | _ => { 256 | uart.write(&ERROR_MSG); 257 | } 258 | } 259 | }); 260 | } 261 | 262 | // same panicking *behavior* as `panic-probe` but doesn't print a panic message 263 | // this prevents the panic message being printed *twice* when `defmt::panic` is invoked 264 | #[defmt::panic_handler] 265 | fn panic() -> ! { 266 | cortex_m::asm::udf() 267 | } -------------------------------------------------------------------------------- /src/spi/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for the Serial Peripheral Interface (SPI) bus peripheral. 2 | //! Provides APIs to configure, read, and write from 3 | //! SPI, with blocking, nonblocking, and DMA functionality. 4 | 5 | use core::ops::Deref; 6 | 7 | cfg_if::cfg_if! { 8 | if #[cfg(any(feature = "h5", feature = "h7"))] { 9 | mod h; 10 | pub use h::*; 11 | } else { 12 | mod baseline; 13 | pub use baseline::*; 14 | } 15 | } 16 | 17 | use cfg_if::cfg_if; 18 | 19 | cfg_if! { 20 | if #[cfg(feature = "c0")] { // pac bug? 21 | use crate::{pac::{self, DMA as DMA1}}; 22 | } else { 23 | use crate::pac::{self, DMA1}; 24 | } 25 | } 26 | #[cfg(any(feature = "f3", feature = "l4"))] 27 | use crate::dma::DmaInput; 28 | #[cfg(not(any(feature = "f4", feature = "l552")))] 29 | use crate::dma::{self, ChannelCfg, DmaChannel}; 30 | use crate::{error::Result, util::RccPeriph}; // todo temp 31 | 32 | #[macro_export] 33 | macro_rules! check_errors { 34 | ($sr:expr) => { 35 | #[cfg(feature = "h7")] 36 | let crc_error = $sr.crce().bit_is_set(); 37 | #[cfg(not(feature = "h7"))] 38 | let crc_error = $sr.crcerr().bit_is_set(); 39 | 40 | if $sr.ovr().bit_is_set() { 41 | return Err(Error::SpiError(SpiError::Overrun)); 42 | } else if $sr.modf().bit_is_set() { 43 | return Err(Error::SpiError(SpiError::ModeFault)); 44 | } else if crc_error { 45 | return Err(Error::SpiError(SpiError::Crc)); 46 | } 47 | }; 48 | } 49 | 50 | /// SPI error 51 | #[non_exhaustive] 52 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 53 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 54 | pub enum SpiError { 55 | /// Overrun occurred 56 | Overrun, 57 | /// Mode fault occurred 58 | ModeFault, 59 | /// CRC error 60 | Crc, 61 | DuplexFailed, // todo temp? 62 | } 63 | 64 | /// Set the factor to divide the APB clock by to set baud rate. Sets `SPI_CR1` register, `BR` field. 65 | /// On H7, sets CFG1 register, `MBR` field. 66 | #[derive(Copy, Clone)] 67 | #[repr(u8)] 68 | pub enum BaudRate { 69 | Div2 = 0b000, 70 | Div4 = 0b001, 71 | Div8 = 0b010, 72 | Div16 = 0b011, 73 | Div32 = 0b100, 74 | Div64 = 0b101, 75 | Div128 = 0b110, 76 | Div256 = 0b111, 77 | } 78 | 79 | #[derive(Clone, Copy)] 80 | #[repr(u8)] 81 | /// FIFO reception threshold Sets `SPI_CR2` register, `FRXTH` field. 82 | pub enum ReceptionThresh { 83 | /// RXNE event is generated if the FIFO level is greater than or equal to 1/2 (16-bit) 84 | D16 = 0, 85 | /// RXNE event is generated if the FIFO level is greater than or equal to 1/4 (8-bit) 86 | D8 = 1, 87 | } 88 | 89 | #[derive(Clone, Copy, PartialEq)] 90 | /// Select the duplex communication mode between the 2 devices. Sets `CR1` register, `BIDIMODE`, 91 | /// and `RXONLY` fields. 92 | pub enum SpiCommMode { 93 | FullDuplex, 94 | HalfDuplex, 95 | /// Simplex Transmit only. (Cfg same as Full Duplex, but ignores input) 96 | TransmitOnly, 97 | /// Simplex Receive only. 98 | ReceiveOnly, 99 | } 100 | 101 | #[derive(Clone, Copy, PartialEq)] 102 | /// Used for managing NSS / CS pin. Sets CR1 register, SSM field. 103 | /// On H7, sets CFG2 register, `SSOE` field. 104 | pub enum SlaveSelect { 105 | /// In this configuration, slave select information 106 | /// is driven internally by the SSI bit value in register SPIx_CR1. The external NSS pin is 107 | /// free for other application uses. 108 | Software, 109 | /// This configuration is only used when the 110 | /// MCU is set as master. The NSS pin is managed by the hardware. The NSS signal 111 | /// is driven low as soon as the SPI is enabled in master mode (SPE=1), and is kept 112 | /// low until the SPI is disabled (SPE =0). A pulse can be generated between 113 | /// continuous communications if NSS pulse mode is activated (NSSP=1). The SPI 114 | /// cannot work in multimaster configuration with this NSS setting. 115 | HardwareOutEnable, 116 | /// If the microcontroller is acting as the 117 | /// master on the bus, this configuration allows multimaster capability. If the NSS pin 118 | /// is pulled low in this mode, the SPI enters master mode fault state and the device is 119 | /// automatically reconfigured in slave mode. In slave mode, the NSS pin works as a 120 | /// standard “chip select” input and the slave is selected while NSS line is at low level. 121 | HardwareOutDisable, 122 | } 123 | 124 | cfg_if! { 125 | if #[cfg(feature = "embedded_hal")] { 126 | type SpiModeType = embedded_hal::spi::Mode; 127 | } else { 128 | #[derive(Clone, Copy)] 129 | #[repr(u8)] 130 | /// Clock polarity. Sets CFGR2 register, CPOL field. Stored in the config as a field of `SpiMode`. 131 | pub enum SpiPolarity { 132 | /// Clock signal low when idle 133 | IdleLow = 0, 134 | /// Clock signal high when idle 135 | IdleHigh = 1, 136 | } 137 | 138 | #[derive(Clone, Copy)] 139 | #[repr(u8)] 140 | /// Clock phase. Sets CFGR2 register, CPHA field. Stored in the config as a field of `SpiMode`. 141 | pub enum SpiPhase { 142 | /// Data in "captured" on the first clock transition 143 | CaptureOnFirstTransition = 0, 144 | /// Data in "captured" on the second clock transition 145 | CaptureOnSecondTransition = 1, 146 | } 147 | 148 | #[derive(Clone, Copy)] 149 | /// SPI mode. Sets CFGR2 reigster, CPOL and CPHA fields. 150 | pub struct SpiMode { 151 | /// Clock polarity 152 | pub polarity: SpiPolarity, 153 | /// Clock phase 154 | pub phase: SpiPhase, 155 | } 156 | 157 | impl SpiMode { 158 | /// Set Spi Mode 0: Idle low, capture on first transition. 159 | /// Data sampled on rising edge and shifted out on the falling edge 160 | pub fn mode0() -> Self { 161 | Self { 162 | polarity: SpiPolarity::IdleLow, 163 | phase: SpiPhase::CaptureOnFirstTransition, 164 | } 165 | } 166 | 167 | /// Set Spi Mode 1: Idle low, capture on second transition. 168 | /// Data sampled on the falling edge and shifted out on the rising edge 169 | pub fn mode1() -> Self { 170 | Self { 171 | polarity: SpiPolarity::IdleLow, 172 | phase: SpiPhase::CaptureOnSecondTransition, 173 | } 174 | } 175 | 176 | /// Set Spi Mode 2: Idle high, capture on first transition. 177 | /// Data sampled on the rising edge and shifted out on the falling edge 178 | pub fn mode2() -> Self { 179 | Self { 180 | polarity: SpiPolarity::IdleHigh, 181 | phase: SpiPhase::CaptureOnFirstTransition, 182 | } 183 | } 184 | 185 | /// Set Spi Mode 3: Idle high, capture on second transition. 186 | /// Data sampled on the falling edge and shifted out on the rising edge 187 | pub fn mode3() -> Self { 188 | Self { 189 | polarity: SpiPolarity::IdleHigh, 190 | phase: SpiPhase::CaptureOnSecondTransition, 191 | } 192 | } 193 | } 194 | 195 | type SpiModeType = SpiMode; 196 | } 197 | } 198 | 199 | #[derive(Clone)] 200 | /// Configuration data for SPI. 201 | pub struct SpiConfig { 202 | /// SPI mode associated with Polarity and Phase. Defaults to Mode0: Idle low, capture on first transition. 203 | pub mode: SpiModeType, 204 | /// Sets the (duplex) communication mode between the devices. Defaults to full duplex. 205 | pub comm_mode: SpiCommMode, 206 | /// Controls use of hardware vs software CS/NSS pin. Defaults to software. 207 | pub slave_select: SlaveSelect, 208 | /// Data size. Defaults to 8 bits. 209 | pub data_size: DataSize, 210 | /// FIFO reception threshhold. Defaults to 8 bits. 211 | pub fifo_reception_thresh: ReceptionThresh, 212 | // pub cs_delay: f32, 213 | // pub swap_miso_mosi: bool, 214 | // pub suspend_when_inactive: bool, 215 | } 216 | 217 | impl Default for SpiConfig { 218 | fn default() -> Self { 219 | cfg_if! { 220 | if #[cfg(feature = "embedded_hal")] { 221 | let mode0 = embedded_hal::spi::MODE_0; 222 | } else { 223 | let mode0 = SpiModeType::mode0(); 224 | } 225 | } 226 | 227 | Self { 228 | mode: mode0, 229 | comm_mode: SpiCommMode::FullDuplex, 230 | slave_select: SlaveSelect::Software, 231 | data_size: DataSize::D8, 232 | fifo_reception_thresh: ReceptionThresh::D8, 233 | } 234 | } 235 | } 236 | 237 | /// Represents a Serial Peripheral Interface (SPI) peripheral. 238 | pub struct Spi { 239 | pub regs: R, 240 | pub cfg: SpiConfig, 241 | } 242 | 243 | impl Spi 244 | where 245 | R: Deref + RccPeriph, 246 | { 247 | /// Stop a DMA transfer. Stops the channel, and disables the `txdmaen` and `rxdmaen` bits. 248 | /// Run this after each transfer completes - you may wish to do this in an interrupt 249 | /// (eg DMA transfer complete) instead of blocking. `channel2` is an optional second channel 250 | /// to stop; eg if you have both a tx and rx channel. 251 | #[cfg(not(any(feature = "f4", feature = "l552")))] 252 | pub fn stop_dma( 253 | &mut self, 254 | channel: DmaChannel, 255 | channel2: Option, 256 | dma_periph: dma::DmaPeriph, 257 | ) -> Result<()> { 258 | // (RM:) To close communication it is mandatory to follow these steps in order: 259 | // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. 260 | 261 | dma::stop(dma_periph, channel)?; 262 | if let Some(ch2) = channel2 { 263 | dma::stop(dma_periph, ch2)?; 264 | }; 265 | 266 | // 2. Disable the SPI by following the SPI disable procedure: 267 | // self.disable(); 268 | 269 | // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the 270 | // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. 271 | 272 | #[cfg(not(feature = "h7"))] 273 | self.regs.cr2().modify(|_, w| { 274 | w.txdmaen().clear_bit(); 275 | w.rxdmaen().clear_bit() 276 | }); 277 | 278 | #[cfg(feature = "h7")] 279 | self.regs.cfg1().modify(|_, w| { 280 | w.txdmaen().clear_bit(); 281 | w.rxdmaen().clear_bit() 282 | }); 283 | 284 | Ok(()) 285 | } 286 | 287 | /// Convenience function that clears the interrupt, and stops the transfer. For use with the TC 288 | /// interrupt only. 289 | #[cfg(not(any(feature = "f4", feature = "l552")))] 290 | pub fn cleanup_dma( 291 | &mut self, 292 | dma_periph: dma::DmaPeriph, 293 | channel_tx: DmaChannel, 294 | channel_rx: Option, 295 | ) -> Result<()> { 296 | // The hardware seems to automatically enable Tx too; and we use it when transmitting. 297 | dma::clear_interrupt(dma_periph, channel_tx, dma::DmaInterrupt::TransferComplete)?; 298 | 299 | if let Some(ch_rx) = channel_rx { 300 | dma::clear_interrupt(dma_periph, ch_rx, dma::DmaInterrupt::TransferComplete)?; 301 | } 302 | 303 | self.stop_dma(channel_tx, channel_rx, dma_periph) 304 | } 305 | 306 | /// Print the (raw) contents of the status register. 307 | pub fn read_status(&self) -> u32 { 308 | // todo july 2025? into 309 | unsafe { self.regs.sr().read().bits().into() } 310 | } 311 | } 312 | --------------------------------------------------------------------------------