├── util ├── st-template │ ├── defmt.toml │ ├── Embed.toml │ ├── memory.x │ ├── cargo-generate.toml │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ └── main.rs │ ├── examples │ │ ├── i2c.rs │ │ └── who_am_i.rs │ ├── README.md │ └── pre-script.rhai ├── st-mems-reg-config-conv │ ├── src │ │ ├── lib.rs │ │ ├── ucf_entry.rs │ │ └── parser.rs │ ├── Cargo.toml │ ├── CHANGELOG.md │ └── README.md ├── st-fifo-tool │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── sensor_data.rs │ │ └── lib.rs ├── st-mems-bus │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── spi.rs │ │ ├── i2c.rs │ │ └── lib.rs └── st-mem-bank-macro │ ├── Cargo.toml │ ├── src │ ├── attributes │ │ ├── mod.rs │ │ ├── struct_register.rs │ │ ├── register.rs │ │ ├── adv_register.rs │ │ ├── named_register.rs │ │ └── mem_bank.rs │ ├── parser.rs │ ├── lib.rs │ └── generator.rs │ └── README.md ├── LICENSE.md ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md └── README.md /util/st-template/defmt.toml: -------------------------------------------------------------------------------- 1 | [defmt] 2 | # defmt logging level 3 | DEFMT_LOG = "debug" 4 | -------------------------------------------------------------------------------- /util/st-mems-reg-config-conv/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[cfg(feature = "std")] 4 | extern crate std; 5 | 6 | pub mod ucf_entry; 7 | #[cfg(feature = "std")] 8 | pub mod parser; 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | | Component | Copyright | License | 2 | | ------------- | ------------------ | ------------ | 3 | | Examples | STMicroelectronics | SLA0094 | 4 | | Tutorials | STMicroelectronics | BSD-3-Clause | 5 | | Host firmware | STMicroelectronics | SLA0094 | 6 | | Model zoo | STMicroelectronics | SLA0094 | 7 | -------------------------------------------------------------------------------- /util/st-fifo-tool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "st-fifo-tool" 3 | version = "1.0.0" 4 | edition = "2024" 5 | readme = "README.md" 6 | keywords = ["st", "no-std", "fifo"] 7 | categories = ["embedded", "no-std"] 8 | description = "A set of utilities useful to interface with the ST MEMS TAG-based IMUs sensor FIFO" 9 | license = "BSD-3-Clause" 10 | repository = "https://github.com/STMicroelectronics/st-mems-rust-drivers/tree/main/util/st-fifo-tool" 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /util/st-template/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.probe] 2 | protocol = "Swd" 3 | speed = 4000 4 | 5 | [default.flashing] 6 | enabled = true 7 | halt_afterwards = false 8 | 9 | [default.reset] 10 | enabled = true 11 | halt_afterwards = false 12 | 13 | [default.general] 14 | chip = "{{mcu}}" 15 | log_level = "INFO" 16 | 17 | [default.rtt] 18 | enabled = true 19 | up_mode = "BlockIfFull" 20 | timeout = 3000 21 | 22 | # Optional GDB server configuration 23 | [default.gdb] 24 | enabled = false 25 | gdb_connection_string = "127.0.0.1:3333" 26 | -------------------------------------------------------------------------------- /util/st-mems-bus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "st-mems-bus" 3 | version = "1.0.1" 4 | edition = "2024" 5 | readme = "README.md" 6 | keywords = ["st", "no-std", "i2c", "spi", "embedded"] 7 | categories = ["embedded", "no-std"] 8 | description = "Wrapper for I2C and SPI buses, providing a unified API to the underlying bus." 9 | license = "BSD-3-Clause" 10 | repository = "https://github.com/STMicroelectronics/st-mems-rust-drivers/tree/main/util/st-mems-bus" 11 | 12 | [dependencies] 13 | embedded-hal = "1.0.0" 14 | 15 | [features] 16 | default = ["spi", "i2c"] 17 | spi = [] 18 | i2c = [] 19 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "st-mem-bank-macro" 3 | version = "1.0.0" 4 | edition = "2024" 5 | readme = "README.md" 6 | license = "BSD-3-Clause" 7 | keywords = ["st", "macro", "driver"] 8 | categories = ["embedded", "no-std"] 9 | description = "Simplifies memory state management for sensors with multiple memory banks by providing useful macros." 10 | repository = "https://github.com/STMicroelectronics/st-mems-rust-drivers/tree/main/util/st-mem-bank-macro" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "2.0", features = ["full", "extra-traits"] } 17 | quote = "1.0" 18 | proc-macro2 = "1.0" 19 | -------------------------------------------------------------------------------- /util/st-template/memory.x: -------------------------------------------------------------------------------- 1 | /* Linker script for STM32 microcontrollers */ 2 | MEMORY 3 | { 4 | /* Main Flash memory */ 5 | FLASH : ORIGIN = 0x08000000, LENGTH = {{flash_size_kb}}K 6 | 7 | /* Main RAM (includes SRAM, and on F7/H7 includes DTCM in the total) */ 8 | RAM : ORIGIN = 0x20000000, LENGTH = {{ram_kb}}K 9 | 10 | {% if ccm_kb != 0 -%} 11 | /* Core Coupled Memory (CCM) - F3/F4 series */ 12 | CCRAM : ORIGIN = 0x10000000, LENGTH = {{ccm_kb}}K 13 | {% endif -%} 14 | } 15 | 16 | /* This is where the call stack will be allocated. */ 17 | /* The stack is of the full descending type. */ 18 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); 19 | 20 | /* Define heap start (optional, adjust as needed) */ 21 | _heap_start = ORIGIN(RAM); 22 | _heap_end = ORIGIN(RAM) + LENGTH(RAM); 23 | -------------------------------------------------------------------------------- /util/st-mems-reg-config-conv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "st-mems-reg-config-conv" 3 | version = "1.0.2" 4 | edition = "2024" 5 | readme = "README.md" 6 | keywords = ["st", "no-std", "ucf", "json", "embedded"] 7 | categories = ["embedded", "no-std"] 8 | description = "A no_std-compatible library to convert MEMS Configuration Shared Types v2.0 into Rust code at build time." 9 | license = "BSD-3-Clause" 10 | repository = "https://github.com/STMicroelectronics/st-mems-rust-drivers/tree/main/util/st-mems-reg-config-conv" 11 | 12 | [lib] 13 | name = "st_mems_reg_config_conv" 14 | 15 | [features] 16 | default = [] 17 | std = ["dep:serde_json", "dep:serde"] 18 | 19 | [dependencies] 20 | serde = {version = "1.0.219", features = ["derive"], optional = true } 21 | serde_json = { version = "1.0.140", optional = true} 22 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/attributes/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | pub mod register; 4 | pub mod adv_register; 5 | pub mod struct_register; 6 | pub mod named_register; 7 | pub mod mem_bank; 8 | 9 | use quote::quote; 10 | 11 | 12 | #[derive(Clone, Copy, PartialEq, Eq)] 13 | pub (crate) enum Order { 14 | Forward, 15 | Inverse 16 | } 17 | 18 | impl Order { 19 | pub fn from_x_bytes_word(&self) -> TokenStream { 20 | if *self == Order::Forward { 21 | quote! {from_le_bytes} 22 | } else { 23 | quote! {from_be_bytes} 24 | } 25 | } 26 | 27 | pub fn to_x_bytes_word(&self) -> TokenStream { 28 | if *self == Order::Forward { 29 | quote! {to_le_bytes} 30 | } else { 31 | quote! {to_be_bytes} 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | out/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | # MSVC Windows builds of rustc generate these, which store debugging information 15 | *.pdb 16 | 17 | # RustRover 18 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 19 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 20 | # and can be added to the global gitignore or merged into this file. For a more nuclear 21 | # option (not recommended) you can uncomment the following to ignore the entire idea folder 22 | #.idea/ 23 | -------------------------------------------------------------------------------- /util/st-mems-reg-config-conv/src/ucf_entry.rs: -------------------------------------------------------------------------------- 1 | pub struct UcfLineExt { 2 | pub address: u8, 3 | pub data: u8, 4 | pub op: MemsUcfOp 5 | } 6 | 7 | #[repr(u8)] 8 | #[derive(Clone, Debug, Copy, PartialEq)] 9 | pub enum MemsUcfOp { 10 | Read = 0x0, 11 | Write = 0x1, 12 | Delay = 0x2, 13 | PollSet = 0x3, 14 | PollReset = 0x4 15 | } 16 | 17 | impl From for MemsUcfOp { 18 | fn from(value: u8) -> Self{ 19 | unsafe { 20 | core::mem::transmute(value) 21 | } 22 | } 23 | } 24 | 25 | impl MemsUcfOp { 26 | pub fn to_string(&self) -> &str { 27 | match self { 28 | MemsUcfOp::Read => "Read", 29 | MemsUcfOp::Write => "Write", 30 | MemsUcfOp::Delay => "Delay", 31 | MemsUcfOp::PollSet => "PollSet", 32 | MemsUcfOp::PollReset => "PollReset" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /util/st-template/cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.10.0" 3 | 4 | [placeholders.framework] 5 | type = "string" 6 | prompt = "Which framework would you like to use?" 7 | choices = ["stm32rs", "embassy"] 8 | default = "embassy" 9 | 10 | [placeholders.mcu] 11 | type = "string" 12 | prompt = "Which STM32 MCU are you targeting?" 13 | default = "stm32f401re" 14 | 15 | [placeholders.sensor] 16 | type = "string" 17 | prompt = "Which sensor do you want?" 18 | choices = [ 19 | "no_sensor", 20 | "lps22hh", 21 | "lsm6dso16is", 22 | "lsm6dsv16x", 23 | "lsm6dsv320x", 24 | "lsm6dsv80x", 25 | "iis2dlpc", 26 | "iis2dulpx", 27 | "iis2mdc", 28 | "ilps22qs", 29 | "ism330dhcx", 30 | "ism330is", 31 | "lis2dux12", 32 | "lis2duxs12", 33 | "lis2mdl", 34 | "lps22df", 35 | "ism6hg256x" 36 | ] 37 | default = "no_sensor" 38 | 39 | [conditional.'framework == "stm32rs"'] 40 | ignore = [ 41 | "defmt.toml" 42 | ] 43 | 44 | [conditional.'framework == "embassy"'] 45 | ignore = [ 46 | "memory.x" 47 | ] 48 | 49 | 50 | [conditional.'sensor == "no_sensor"'] 51 | ignore = [ 52 | "src/who_am_i.rs" 53 | ] 54 | 55 | [hooks] 56 | pre = ["pre-script.rhai"] 57 | -------------------------------------------------------------------------------- /util/st-mems-bus/README.md: -------------------------------------------------------------------------------- 1 | # st-mems-bus 2 | [![Crates.io][crates-badge]][crates-url] 3 | [![BSD 3-Clause licensed][bsd-badge]][bsd-url] 4 | 5 | [crates-badge]: https://img.shields.io/crates/v/st-mems-bus 6 | [crates-url]: https://crates.io/crates/st-mems-bus 7 | [bsd-badge]: https://img.shields.io/crates/l/st-mems-bus 8 | [bsd-url]: https://opensource.org/licenses/BSD-3-Clause 9 | 10 | The st-mems-bus Library provides a unified and consistent API for accessing different types of communication buses. Currently, it supports both `SPI` and `I2C` buses, offering various modes for managing bus ownership and access. 11 | 12 | ## Access Modes 13 | 14 | - **shared**: 15 | This mode uses `RefCell` internally and calls `borrow_mut()` to ensure exclusive mutable access to the bus at runtime. While this introduces some overhead, it provides a simple mechanism to safely share the bus. More advanced sharing techniques are left to the user to implement as needed. 16 | 17 | ## Usage 18 | 19 | Add the library to your dependencies in `Cargo.toml`: 20 | 21 | ```toml 22 | [dependencies] 23 | st-mems-bus = { path = "path_to_bus" } 24 | ``` 25 | 26 | ## Features 27 | To keep the library lightweight, you can enable support for each bus type individually. By default, all bus types are included. Available features: 28 | 29 | - **spi** - Enable support for SPI bus. 30 | - **i2c** - Enable support for I2C bus. 31 | 32 | ------ 33 | 34 | **More information: [http://www.st.com](http://st.com/MEMS)** 35 | 36 | **Copyright © 2025 STMicroelectronics** 37 | -------------------------------------------------------------------------------- /util/st-template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name}}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "{{project-name}}" 8 | 9 | [[example]] 10 | name = "i2c" 11 | path = "examples/i2c.rs" 12 | 13 | [[example]] 14 | name = "who_am_i" 15 | path = "examples/who_am_i.rs" 16 | 17 | [dependencies] 18 | cortex-m-rt = "0.7" 19 | panic-halt = "0.2" 20 | 21 | {% if framework == "stm32rs" -%} 22 | cortex-m = "0.7" 23 | # STM32 HAL dependencies 24 | {% if mcu == "stm32f401re" -%} 25 | stm32f4xx-hal = { version = "0.21", features = ["stm32f401", "defmt"] } 26 | {% endif -%} 27 | nb = "1.0" 28 | panic-probe = { version = "1.0.0", features = ["print-defmt"] } 29 | {% endif -%} 30 | 31 | {% if sensor != "no_sensor" -%} 32 | {{sensor}}-rs = "1.0.0" 33 | {% endif -%} 34 | 35 | defmt = "0.3" 36 | defmt-rtt = "0.4" 37 | 38 | {% if framework == "embassy" -%} 39 | cortex-m = { version = "0.7", features = ["inline-asm", "critical-section-single-core"] } 40 | # Embassy dependencies 41 | embassy-stm32 = { version = "0.1", features = ["{{mcu}}", "time-driver-any", "exti", "memory-x"] } 42 | embassy-executor = { version = "0.6", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } 43 | embassy-time = { version = "0.3", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } 44 | embassy-futures = "0.1" 45 | static_cell = "2" 46 | {% endif -%} 47 | 48 | [build-dependencies] 49 | reqwest = { version = "0.12", features = ["blocking"] } 50 | 51 | # Optional features 52 | [features] 53 | default = [] 54 | 55 | # Profile optimizations 56 | [profile.release] 57 | codegen-units = 1 58 | debug = 2 59 | debug-assertions = false 60 | incremental = false 61 | lto = true 62 | opt-level = 3 63 | overflow-checks = false 64 | 65 | [profile.dev] 66 | debug = true 67 | lto = false 68 | opt-level = 1 69 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lsm6dsv320x"] 2 | path = lsm6dsv320x 3 | url = https://github.com/STMicroelectronics/lsm6dsv320x-rs 4 | [submodule "lsm6dsv16x"] 5 | path = lsm6dsv16x 6 | url = https://github.com/STMicroelectronics/lsm6dsv16x-rs 7 | [submodule "lis2duxs12"] 8 | path = lis2duxs12 9 | url = https://github.com/STMicroelectronics/lis2duxs12-rs 10 | [submodule "lis2dux12"] 11 | path = lis2dux12 12 | url = https://github.com/STMicroelectronics/lis2dux12-rs 13 | [submodule "lis2mdl"] 14 | path = lis2mdl 15 | url = https://github.com/STMicroelectronics/lis2mdl-rs 16 | [submodule "lsm6dsv80x"] 17 | path = lsm6dsv80x 18 | url = https://github.com/STMicroelectronics/lsm6dsv80x-rs 19 | [submodule "lps22df"] 20 | path = lps22df 21 | url = https://github.com/STMicroelectronics/lps22df-rs 22 | [submodule "lsm6dso16is"] 23 | path = lsm6dso16is 24 | url = https://github.com/STMicroelectronics/lsm6dso16is-rs 25 | [submodule "ism330is"] 26 | path = ism330is 27 | url = https://github.com/STMicroelectronics/ism330is-rs 28 | [submodule "ism330dhcx"] 29 | path = ism330dhcx 30 | url = https://github.com/STMicroelectronics/ism330dhcx-rs 31 | [submodule "iis2dlpc"] 32 | path = iis2dlpc 33 | url = https://github.com/STMicroelectronics/iis2dlpc-rs 34 | [submodule "iis2dulpx"] 35 | path = iis2dulpx 36 | url = https://github.com/STMicroelectronics/iis2dulpx-rs 37 | [submodule "iis2mdc"] 38 | path = iis2mdc 39 | url = https://github.com/STMicroelectronics/iis2mdc-rs 40 | [submodule "ilps22qs"] 41 | path = ilps22qs 42 | url = https://github.com/STMicroelectronics/ilps22qs-rs 43 | [submodule "lps22hh"] 44 | path = lps22hh 45 | url = https://github.com/STMicroelectronics/lps22hh-rs 46 | [submodule "stts22h"] 47 | path = stts22h 48 | url = https://github.com/STMicroelectronics/stts22h-rs 49 | [submodule "ism6hg256x"] 50 | path = ism6hg256x 51 | url = git@github.com:STMicroelectronics/ism6hg256x-rs.git 52 | -------------------------------------------------------------------------------- /util/st-mems-reg-config-conv/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v [1.0.2](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/06d6a2983f897f689466e94487a9aaf21a82965d) 2 | 3 | ### Fix 4 | - 'outputs' field marked as optional [b3a7477](b3a7477de22905498f8759ad041ba1a8809dec54) 5 | 6 | ## v [1.0.1](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/46f906901a51ce3b3a96c1d7be03c3b1b3fa14b3) 7 | 8 | ### Added 9 | - Added Foreign Function Interface (FFI) [6e7456e](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/6e7456e60e23b33497a0868308a0253f6ed0d917) 10 | 11 | ### Fix 12 | - Description field marked as optional [a4b0598](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/a4b059898e97f246562b46b7ff87ee5e3fda76bb): 13 | Some JSON configuration doesn't have this field 14 | - Fix clippy errors [b7f227d](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/b7f227d7d3ab253122ed156ae2423ecdeb4d3a6a) 15 | - Fix README.md name [60a4af7](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/60a4af75ffbccafc44a529f72f6cdc64a76e5f9d): 16 | Changed REAMDE.md into README.md 17 | 18 | ### Changed 19 | - Updated README.md [9cf34bc](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/9cf34bccb1ed183ae81e7919fd500c0395afa12a): 20 | - Added badges for crate version and license 21 | - Added copyright line 22 | 23 | ## v [1.0.0](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/047cbab00cdc192e690079a09e52102cf973d3fa) 24 | 25 | ### Fix 26 | - Fixed register name in the generated file [fc3fd45](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/fc3fd4595669e08920242327357be896b0c18781) 27 | 28 | ### Added 29 | 30 | - Added rustfmt:skip into generated file [8966f89](https://github.com/STMicroelectronics/st-mems-rust-drivers/commit/8966f898763af847ec0a49f83ecb3bb01ab5a63e) 31 | 32 | - Add register configuration converter library 33 | - Fixed crate used 34 | - Added rustfmt:skip 35 | - Updated the versions to 1.0.0 36 | - Change license string in Cargo.toml 37 | - Fix README.md name 38 | - Updated README 39 | - Added Foreign Function Interface (FFI) 40 | - Fix clippy errors 41 | - Optional description and changed crate-type 42 | -------------------------------------------------------------------------------- /util/st-template/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | {% if framework == "stm32rs" -%} 3 | use std::env; 4 | use std::path::{PathBuf, Path}; 5 | use std::fs::{self, File}; 6 | {% else -%} 7 | use std::fs; 8 | use std::path::Path; 9 | {% endif -%} 10 | 11 | 12 | fn main() { 13 | {% if framework == "stm32rs" -%} 14 | // Put `memory.x` in our output directory and ensure it's 15 | // on the linker search path. 16 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 17 | File::create(out.join("memory.x")) 18 | .unwrap() 19 | .write_all(include_bytes!("memory.x")) 20 | .unwrap(); 21 | println!("cargo:rustc-link-search={}", out.display()); 22 | 23 | // By default, Cargo will re-run a build script whenever 24 | // any file in the project changes. By specifying `memory.x` 25 | // here, we ensure the build script is only re-run when 26 | // `memory.x` is changed. 27 | println!("cargo:rerun-if-changed=memory.x"); 28 | {% endif -%} 29 | // Specify linker arguments. 30 | 31 | // `--nmagic` is required if memory section addresses are not aligned to 0x10000, 32 | // for example the FLASH and RAM sections in your `memory.x`. 33 | // See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 34 | println!("cargo:rustc-link-arg=--nmagic"); 35 | 36 | // Set the linker script to the one provided by cortex-m-rt. 37 | println!("cargo:rustc-link-arg=-Tlink.x"); 38 | println!("cargo:rustc-link-arg=-Tdefmt.x"); 39 | 40 | // Retrive the taget chip 41 | let chip = "{{svd_name}}.svd"; 42 | 43 | let file_name = "{{svd_name}}.svd"; 44 | let string_path = format!(".vscode/{file_name}"); 45 | let output_path = Path::new(&string_path); 46 | let url = format!("https://stm32-rs.github.io/stm32-rs/{chip}.patched"); 47 | 48 | if output_path.exists() { 49 | println!("SVD already exitsts, skipping download"); 50 | } else { 51 | let response = reqwest::blocking::get(&url).unwrap(); 52 | let content = response.text().unwrap(); 53 | 54 | let mut file = fs::File::create(output_path).unwrap(); 55 | file.write_all(content.as_bytes()).unwrap(); 56 | 57 | println!("SVD created at {:?}", output_path); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /util/st-template/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Blink example 2 | //! 3 | //! This example blink a led on a board 4 | //! 5 | //! The reference board used is the NUCLEO-F401RE; other boards may require 6 | //! setting the pins accordingly. 7 | //! 8 | //! Default pins: PA5 (LED) 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | {% if framework == "stm32rs" -%} 14 | use {defmt_rtt as _, panic_probe as _}; 15 | use cortex_m_rt::entry; 16 | use stm32f4xx_hal::{ 17 | pac, 18 | prelude::*, 19 | }; 20 | use defmt::*; 21 | 22 | #[entry] 23 | fn main() -> ! { 24 | // Get access to the core peripherals from the cortex-m crate 25 | let cp = cortex_m::Peripherals::take().unwrap(); 26 | // Get access to the device specific peripherals from the peripheral access crate 27 | let dp = pac::Peripherals::take().unwrap(); 28 | 29 | // Take ownership over the raw flash and rcc devices and convert them into the corresponding 30 | // HAL structs 31 | let rcc = dp.RCC.constrain(); 32 | let clocks = rcc.cfgr.use_hse(8.MHz()).sysclk(48.MHz()).freeze(); 33 | 34 | // Acquire the GPIO peripheral 35 | let gpioa = dp.GPIOA.split(); 36 | 37 | // Configure PA5 as a push-pull output 38 | let mut led = gpioa.pa5.into_push_pull_output(); 39 | 40 | // Create a delay abstraction based on SysTick 41 | let mut delay = cp.SYST.delay(&clocks); 42 | delay.delay_ms(5); 43 | 44 | info!("Start toggling the led"); 45 | 46 | loop { 47 | // Toggle the LED 48 | led.toggle(); 49 | 50 | // Wait for 100 ms 51 | delay.delay_ms(100); 52 | } 53 | } 54 | {% endif -%} 55 | 56 | {% if framework == "embassy" -%} 57 | use defmt::*; 58 | use embassy_executor::Spawner; 59 | use embassy_stm32::gpio::{Level, Output, Speed}; 60 | use embassy_time::Timer; 61 | use {defmt_rtt as _, panic_halt as _}; 62 | 63 | #[embassy_executor::main] 64 | async fn main(_spawner: Spawner) { 65 | info!("Hello World!"); 66 | 67 | let p = embassy_stm32::init(Default::default()); 68 | 69 | // Configure PA5 as output 70 | let mut led = Output::new(p.PA5, Level::High, Speed::Low); 71 | 72 | loop { 73 | info!("high"); 74 | led.set_high(); 75 | Timer::after_millis(300).await; 76 | 77 | info!("low"); 78 | led.set_low(); 79 | Timer::after_millis(300).await; 80 | } 81 | } 82 | {% endif -%} 83 | -------------------------------------------------------------------------------- /util/st-mems-bus/src/spi.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::spi::{SpiDevice, Operation}; 2 | use crate::BusOperation; 3 | 4 | pub struct SpiBus

{ 5 | pub spi: P 6 | } 7 | 8 | #[allow(dead_code)] 9 | impl SpiBus

{ 10 | /// Create new Spi instance 11 | /// 12 | /// # Arguments 13 | /// 14 | /// * `spi`: Instance of embedded hal SpiDevice 15 | /// 16 | /// # Returns 17 | /// 18 | /// * `Self` 19 | pub fn new(spi: P) -> Self { 20 | Self { spi } 21 | } 22 | } 23 | 24 | impl BusOperation for SpiBus

{ 25 | type Error = P::Error; 26 | 27 | /// Reads bytes from the SPI bus. 28 | /// 29 | /// # Arguments 30 | /// 31 | /// * `rbuf`: Buffer to store the read bytes. 32 | /// 33 | /// # Returns 34 | /// 35 | /// * `Result` 36 | /// * `()` 37 | /// * `Err`: Returns an error if the read operation fails. 38 | #[inline] 39 | fn read_bytes(&mut self, rbuf: &mut [u8]) -> Result<(), Self::Error> { 40 | self.spi.transaction(&mut [Operation::Read(rbuf)])?; 41 | 42 | Ok(()) 43 | } 44 | 45 | /// Writes bytes to the SPI bus. 46 | /// 47 | /// # Arguments 48 | /// 49 | /// * `wbuf`: Buffer containing the bytes to write. 50 | /// 51 | /// # Returns 52 | /// 53 | /// * `Result` 54 | /// * `()` 55 | /// * `Err`: Returns an error if the write operation fails. 56 | #[inline] 57 | fn write_bytes(&mut self, wbuf: &[u8]) -> Result<(), Self::Error> { 58 | self.spi.transaction(&mut [Operation::Write(wbuf)])?; 59 | 60 | Ok(()) 61 | } 62 | 63 | /// Writes a byte and then reads bytes from the SPI bus. 64 | /// 65 | /// # Arguments 66 | /// 67 | /// * `wbuf`: Buffer containing the byte to write. 68 | /// * `rbuf`: Buffer to store the read bytes. 69 | /// 70 | /// # Returns 71 | /// 72 | /// * `Result` 73 | /// * `()` 74 | /// * `Err`: Returns an error if the write-read operation fails. 75 | #[inline] 76 | fn write_byte_read_bytes( 77 | &mut self, 78 | wbuf: &[u8; 1], 79 | rbuf: &mut [u8], 80 | ) -> Result<(), Self::Error> { 81 | self.spi 82 | .transaction(&mut [Operation::Write(&[wbuf[0] | 0x80]), Operation::Read(rbuf)])?; 83 | 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /util/st-mems-bus/src/i2c.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::i2c::{I2c, SevenBitAddress}; 2 | use crate::BusOperation; 3 | 4 | pub struct I2cBus { 5 | pub i2c: T, 6 | pub address: SevenBitAddress 7 | } 8 | 9 | #[allow(dead_code)] 10 | impl I2cBus { 11 | /// Create new I2C instance 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `i2c`: Instance of embedded hal I2c 16 | /// * `address`: Address of the i2c 17 | /// 18 | /// # Returns 19 | /// 20 | /// * `Self` 21 | pub fn new(i2c: T, address: SevenBitAddress) -> Self { 22 | Self { i2c, address } 23 | } 24 | } 25 | 26 | impl BusOperation for I2cBus { 27 | type Error = T::Error; 28 | 29 | /// Reads bytes from the I2C bus. 30 | /// 31 | /// # Arguments 32 | /// 33 | /// * `rbuf`: Buffer to store the read bytes. 34 | /// 35 | /// # Returns 36 | /// 37 | /// * `Result` 38 | /// * `()` 39 | /// * `Err`: Returns an error if the read operation fails. 40 | #[inline] 41 | fn read_bytes(&mut self, rbuf: &mut [u8]) -> Result<(), Self::Error> { 42 | self.i2c.read(self.address, rbuf)?; 43 | 44 | Ok(()) 45 | } 46 | 47 | /// Writes bytes to the I2C bus. 48 | /// 49 | /// # Arguments 50 | /// 51 | /// * `wbuf`: Buffer containing the bytes to write. 52 | /// 53 | /// # Returns 54 | /// 55 | /// * `Result` 56 | /// * `()` 57 | /// * `Err`: Returns an error if the write operation fails. 58 | #[inline] 59 | fn write_bytes(&mut self, wbuf: &[u8]) -> Result<(), Self::Error> { 60 | self.i2c.write(self.address, wbuf)?; 61 | 62 | Ok(()) 63 | } 64 | 65 | /// Writes a byte and then reads bytes from the I2C bus. 66 | /// 67 | /// # Arguments 68 | /// 69 | /// * `wbuf`: Buffer containing the byte to write. 70 | /// * `rbuf`: Buffer to store the read bytes. 71 | /// 72 | /// # Returns 73 | /// 74 | /// * `Result` 75 | /// * `()` 76 | /// * `Err`: Returns an error if the write-read operation fails. 77 | #[inline] 78 | fn write_byte_read_bytes( 79 | &mut self, 80 | wbuf: &[u8; 1], 81 | rbuf: &mut [u8], 82 | ) -> Result<(), Self::Error> { 83 | self.i2c.write_read(self.address, wbuf, rbuf)?; 84 | 85 | Ok(()) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /util/st-mems-reg-config-conv/README.md: -------------------------------------------------------------------------------- 1 | # st-mems-reg-config-conv 2 | [![Crates.io][crates-badge]][crates-url] 3 | [![BSD 3-Clause licensed][bsd-badge]][bsd-url] 4 | 5 | [crates-badge]: https://img.shields.io/crates/v/st-mems-reg-config-conv 6 | [crates-url]: https://crates.io/crates/st-mems-reg-config-conv 7 | [bsd-badge]: https://img.shields.io/crates/l/st-mems-reg-config-conv 8 | [bsd-url]: https://opensource.org/licenses/BSD-3-Clause 9 | 10 | The Registers Configuration Converter is designed to facilitate the conversion of JSON/UCF registers configuration files, generated using STMicroelectronics tools, into Rust code. This library streamlines the process of integrating device configurations into Rust projects, supporting no_std environment. 11 | 12 | ## Installation and usage 13 | To use the Registers Configuration Converter in your project, follow these steps: 14 | 15 | ### Step 1: Add Dependency 16 | 17 | Include the Registers Configuration Converter as a dependency in your Cargo.toml file: 18 | 19 | ```[Toml] 20 | [dependencies] 21 | st-mems-reg-config-conv = { version = "1.0.2" } 22 | ``` 23 | 24 | ### Step 2: Enable std Features for Build 25 | 26 | For the build process std is required by the parser. But the library could still compile for no_std projects. 27 | 28 | ```[Toml] 29 | [dependencies] 30 | st-mems-reg-config-conv = { version = "1.0.2", features = ['std'] } 31 | ``` 32 | 33 | ### Step 3: Build script Integration 34 | 35 | In your build script, include the parser; 36 | 37 | ```[Rust] 38 | use st_mems_reg_config_conv::parser; 39 | ``` 40 | 41 | ### Step 4: Build Main Fucntion Implementation 42 | 43 | Inside the build script, add this code in the main function to specify the input and output files alongside the name of the array that will contain the entries. 44 | 45 | ```[Rust] 46 | let input_file = Path::new("path_to_reg_config"); 47 | let output_file = Path::new("src/rs_file_output"); 48 | parser::generate_rs_from_json(&input_file, &output_file, "JsonEntries"); 49 | ``` 50 | 51 | ## Usage in no_std Projects 52 | 53 | The Registers Configuration Converter is designed to be used in no_std projects by default. However, the parsers require linking to the standard library, necessitating the library's inclusion as both a regular dependency and a build dependency. In a std environment, this dual import is not necessary. 54 | 55 | ------ 56 | 57 | **More information: [http://www.st.com](http://st.com/MEMS)** 58 | 59 | **Copyright © 2025 STMicroelectronics** 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing guide 2 | This document serves as a checklist before contributing to this repository. It includes links to additional information if topics are unclear to you. 3 | 4 | This guide mainly focuses on the proper use of Git. 5 | 6 | ### 1. Before opening an issue 7 | To report a bug/request please enter the issue in the right repository. 8 | 9 | Please check the following boxes before posting an issue: 10 | - [ ] `Make sure you are using the latest commit (major releases are Tagged, but corrections are available as new commits).` 11 | - [ ] `Make sure your issue is a question/feedback/suggestion RELATED TO the software provided in this repository.` Otherwise, it should be discussed on the [ST Community forum](https://community.st.com/s/). 12 | - [ ] `Make sure your issue is not already reported/fixed on GitHub or discussed in a previous issue.` Please refer to the tab issue for the list of issues and pull-requests. Do not forget to browse to the **closed** issues. 13 | 14 | ### 2. Posting the issue 15 | When you have checked the previous boxes, you will find two templates (Bug Report or Other Issue) available in the **Issues** tab of the repository. 16 | 17 | ### 3. Pull Requests 18 | STMicroelectronics is happy to receive contributions from the community, based on an initial Contributor License Agreement (CLA) procedure. 19 | 20 | * If you are an individual writing original source code and you are sure **you own the intellectual property**, then you need to sign an Individual CLA (https://cla.st.com). 21 | * If you work for a company that wants also to allow you to contribute with your work, your company needs to provide a Corporate CLA (https://cla.st.com) mentioning your GitHub account name. 22 | * If you are not sure that a CLA (Individual or Corporate) has been signed for your GitHub account, you can check here (https://cla.st.com). 23 | 24 | Please note that: 25 | * The Corporate CLA will always take precedence over the Individual CLA. 26 | * One CLA submission is sufficient for any project proposed by STMicroelectronics. 27 | 28 | #### How to proceed 29 | * We recommend to engage first a communication through an issue, in order to present your proposal and just to confirm that it corresponds to a STMicroelectronics domain or scope. 30 | * Then fork the project to your GitHub account to further develop your contribution. Please use the latest commit version. 31 | * Please submit one Pull Request for one new feature or proposal. This will facilitate the analysis and the final merge if accepted. 32 | 33 | -------------------------------------------------------------------------------- /util/st-fifo-tool/README.md: -------------------------------------------------------------------------------- 1 | # st-fifo-tool 2 | [![Crates.io][crates-badge]][crates-url] 3 | [![BSD 3-Clause licensed][bsd-badge]][bsd-url] 4 | 5 | [crates-badge]: https://img.shields.io/crates/v/st-fifo-tool 6 | [crates-url]: https://crates.io/crates/st-fifo-tool 7 | [bsd-badge]: https://img.shields.io/crates/l/st-fifo-tool 8 | [bsd-url]: https://opensource.org/licenses/BSD-3-Clause 9 | 10 | This repository contains a set of utilities useful to interface with the ST MEMS TAG-based IMUs sensor FIFO: it provides the capability to decode and decompress the data samples. 11 | 12 | ## Api Example 13 | 14 | The APIs are the following: 15 | 16 | ```rust 17 | // Create a st_fifo_tool config: 18 | let config = st_fifo_tool::Config { 19 | device: st_fifo_tool::DeviceType::..., 20 | bdr_xl: ..., 21 | bdr_gy: ..., 22 | bdr_vsens: ... 23 | }; 24 | 25 | // Initialize the st_fifo_tool 26 | let mut fifo = st_fifo_tool::FifoData::init(&config).unwrap(); 27 | 28 | // Initialize structs to hold the input/output data 29 | let SLOT_NUMBER: u8 = ...; 30 | let mut raw_slot = [st_fifo_tool::RawSlot::default(); SLOT_NUMBER as usize]; 31 | let mut out_slot = [st_fifo_tool::OutSlot::default(); SLOT_NUMBER as usize]; 32 | 33 | // Reads data (inside a loop) 34 | let mut out_slot_size: u16 = 0; 35 | let f_data = sensor.fifo_out_raw_get().unwrap(); // get raw data from some sensor 36 | let fifo_status = sensor.fifo_status_get().unwrap(); 37 | let slots = fifo_status.fifo_level; 38 | 39 | for i in 0..slots { 40 | raw_slot[i as usize].fifo_data_out[0] = ((f_data.tag as u8) << 3) | (f_data.cnt << 1); 41 | raw_slot[slots as usize].fifo_data_out[1..].copy_from_slice(&f_data.data); 42 | } 43 | 44 | // Decode and sort the data 45 | fifo.decode(&mut out_slot, &raw_slot, &mut out_slot_size, slots); 46 | fifo.sort(&mut out_slot, out_slot_size); 47 | 48 | // Count how many samples for SensorType 49 | let mut solts_for_acc = 0 // accelerometer or gyroscope 50 | fifo.extract_sensor(&mut slots_for_acc, &out_slot, out_slot_size, st_fifo_tool::SensorType::Accelerometer); 51 | 52 | // Extract samples from the SensorType 53 | fifo.extract_sensor(&mut acc_slot, &out_slot, out_slot_size, st_fifo_tool::SensorType::Accelerometer); 54 | 55 | ``` 56 | 57 | ## Repository overview 58 | 59 | This utility is structured as follows: 60 | 61 | - [lib.rs](./src/lib.rs): Provides the *FifoTool* strucing to decode/sort/extract raw fifo data. *RawSlot* and *OutSlot* are auxiliary structures used as input/output data. 62 | - RawSlot: Used to read the input data to be processed (the example provides full details). 63 | - OutSlot: Contains the output generated after decoding: a timestamp, a sensor tag useful to interpret the SensorData. 64 | - [sensor_data.rs](./src/sensor_data.rs): Defines the *SensorData* struct, it holds the raw data and provide methods to convert to various output depending on the tag value. 65 | 66 | ------ 67 | 68 | **More information: [http://www.st.com](http://st.com/MEMS)** 69 | 70 | **Copyright © 2025 STMicroelectronics** 71 | 72 | -------------------------------------------------------------------------------- /util/st-template/examples/i2c.rs: -------------------------------------------------------------------------------- 1 | //! I2C example 2 | //! 3 | //! This example demonstrates I2C communication 4 | //! 5 | //! The reference board used is the NUCLEO-F401RE; other boards may require 6 | //! setting the pins accordingly. 7 | //! 8 | //! Default pins: PB8 (SCL), PB9 (SDA) 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | {% if framework == "stm32rs" -%} 14 | use defmt::*; 15 | use {defmt_rtt as _, panic_probe as _}; 16 | use cortex_m_rt::entry; 17 | use stm32f4xx_hal::{ 18 | pac, 19 | i2c::I2c, 20 | prelude::*, 21 | }; 22 | 23 | 24 | #[entry] 25 | fn main() -> ! { 26 | let cp = cortex_m::Peripherals::take().unwrap(); 27 | let dp = pac::Peripherals::take().unwrap(); 28 | 29 | let rcc = dp.RCC.constrain(); 30 | let clocks = rcc.cfgr.use_hse(8.MHz()).sysclk(48.MHz()).freeze(); 31 | 32 | let gpiob = dp.GPIOB.split(); 33 | 34 | // Configure I2C pins 35 | let scl = gpiob.pb8.into_alternate().set_open_drain(); 36 | let sda = gpiob.pb9.into_alternate().set_open_drain(); 37 | 38 | // Create I2C interface 39 | let mut i2c = I2c::new(dp.I2C1, (scl, sda), 400.kHz(), &clocks); 40 | 41 | // Wait a boot time 42 | let mut delay = cp.SYST.delay(&clocks); 43 | delay.delay_ms(5); 44 | 45 | // Example: Scan for I2C devices 46 | for addr in 0x08..0x78 { 47 | if i2c.write(addr, &[]).is_ok() { 48 | // Device found at address 49 | // In a real application, you would handle this 50 | info!("Device found at addr: 0x{:02X}", addr); 51 | } 52 | } 53 | 54 | loop {} 55 | } 56 | {% endif -%} 57 | 58 | {% if framework == "embassy" -%} 59 | use defmt::*; 60 | use embassy_executor::Spawner; 61 | use embassy_stm32::{ 62 | i2c::{self, I2c, Config as I2cConfig}, 63 | time::khz, 64 | peripherals, 65 | dma::NoDma, 66 | bind_interrupts 67 | }; 68 | use {defmt_rtt as _, panic_halt as _}; 69 | 70 | #[defmt::panic_handler] 71 | fn panic() -> ! { 72 | core::panic!("panic via `defmt::panic!`") 73 | } 74 | 75 | bind_interrupts!(struct Irqs { 76 | I2C1_EV => i2c::EventInterruptHandler; 77 | I2C1_ER => i2c::ErrorInterruptHandler; 78 | }); 79 | 80 | #[embassy_executor::main] 81 | async fn main(_spawner: Spawner) { 82 | info!("I2C example starting..."); 83 | 84 | let p = embassy_stm32::init(Default::default()); 85 | 86 | let mut i2c: I2c<_> = I2c::new( 87 | p.I2C1, 88 | p.PB8, 89 | p.PB9, 90 | Irqs, 91 | NoDma, 92 | NoDma, 93 | khz(100), 94 | I2cConfig::default(), 95 | ); 96 | 97 | info!("Scanning I2C bus..."); 98 | 99 | // Example: Scan for I2C devices 100 | for addr in 0x08..0x78_u8 { 101 | match i2c.blocking_write(addr, &[]) { 102 | Ok(_) => info!("Device found at address 0x{:02X}", addr), 103 | Err(_) => {}, // No device at this address 104 | } 105 | } 106 | 107 | info!("Search has ended"); 108 | } 109 | {% endif -%} 110 | -------------------------------------------------------------------------------- /util/st-fifo-tool/src/sensor_data.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Default)] 2 | pub struct SensorData { 3 | pub data: [i16; 3], 4 | } 5 | 6 | impl SensorData { 7 | pub fn from_u8_arr(&mut self, data: &[u8]) { 8 | for i in 0..3 { 9 | self.data[i] = (data[i * 2] as i16) << 8 | data[i * 2 + 1] as i16; 10 | } 11 | } 12 | 13 | pub fn to_axis(&self) -> AxisData { 14 | AxisData { 15 | x: self.data[0], 16 | y: self.data[1], 17 | z: self.data[2], 18 | } 19 | } 20 | 21 | pub fn to_temperature(&self) -> TemperatureData { 22 | TemperatureData { 23 | temp: self.data[0] 24 | } 25 | } 26 | 27 | pub fn to_step_counter(&self) -> StepCounterData { 28 | StepCounterData { 29 | steps: self.data[0] as u16, 30 | steps_t: [ 31 | (self.data[1] & 0xFF) as u8, 32 | (self.data[1] >> 8) as u8, 33 | (self.data[2] & 0xFF) as u8, 34 | (self.data[2] >> 8) as u8, 35 | ] 36 | } 37 | } 38 | 39 | pub fn to_quaternion(&self) -> QuaternionData { 40 | QuaternionData { 41 | qx: self.data[0] as u16, 42 | qy: self.data[1] as u16, 43 | qz: self.data[2] as u16, 44 | } 45 | } 46 | 47 | pub fn to_ext_sensor_nack(&self) -> ExtSensorNackData { 48 | ExtSensorNackData { 49 | nack: self.data[0] as u8, 50 | } 51 | } 52 | 53 | pub fn to_mlc_result(&self) -> MlcResultData { 54 | MlcResultData { 55 | mlc_res: (self.data[0] * 0xFF) as u8, 56 | mlc_idx: (self.data[0] >> 8) as u8, 57 | mlc_t: [ 58 | (self.data[1] & 0xFF) as u8, 59 | (self.data[1] >> 8) as u8, 60 | (self.data[2] & 0xFF) as u8, 61 | (self.data[2] >> 8) as u8, 62 | ], 63 | } 64 | } 65 | 66 | pub fn to_mlc_filter_feature(&self) -> MlcFilterFeatureData { 67 | MlcFilterFeatureData { 68 | mlc_value: self.data[0] as u16, 69 | mlc_id: self.data[1] as u16, 70 | reserved: self.data[2] as u16, 71 | } 72 | } 73 | } 74 | 75 | 76 | #[derive(Clone, Copy, Default)] 77 | pub struct AxisData { 78 | pub x: i16, 79 | pub y: i16, 80 | pub z: i16, 81 | } 82 | 83 | #[derive(Clone, Copy, Default)] 84 | pub struct TemperatureData { 85 | pub temp: i16, 86 | } 87 | 88 | #[derive(Clone, Copy, Default)] 89 | pub struct StepCounterData { 90 | pub steps: u16, 91 | pub steps_t: [u8; 4], 92 | } 93 | 94 | #[derive(Clone, Copy, Default)] 95 | pub struct QuaternionData { 96 | pub qx: u16, 97 | pub qy: u16, 98 | pub qz: u16, 99 | } 100 | 101 | #[derive(Clone, Copy, Default)] 102 | pub struct ExtSensorNackData { 103 | pub nack: u8, 104 | } 105 | 106 | #[derive(Clone, Copy, Default)] 107 | pub struct MlcResultData { 108 | pub mlc_res: u8, 109 | pub mlc_idx: u8, 110 | pub mlc_t: [u8; 4], 111 | } 112 | 113 | #[derive(Clone, Copy, Default)] 114 | pub struct MlcFilterFeatureData { 115 | pub mlc_value: u16, 116 | pub mlc_id: u16, 117 | pub reserved: u16, 118 | } 119 | 120 | -------------------------------------------------------------------------------- /util/st-template/README.md: -------------------------------------------------------------------------------- 1 | # {{project-name}} 2 | 3 | A Rust embedded project for STM32 microcontrollers using {%- if framework == "stm32rs" %} STM32 HAL{%- else %} Embassy{%- endif %}. 4 | 5 | ## Hardware Target 6 | 7 | - **MCU**: {{mcu}} 8 | - **Framework**: {{framework}} 9 | 10 | ## Getting Started 11 | 12 | ### Prerequisites 13 | 14 | 1. Install Rust with the embedded target: 15 | ```bash 16 | rustup target add thumbv7em-none-eabihf 17 | ``` 18 | 19 | 2. Install probe-rs for flashing and debugging: 20 | ```bash 21 | cargo install probe-rs-tools --locked 22 | ``` 23 | 24 | ### Building 25 | 26 | ```bash 27 | cargo build --release 28 | ``` 29 | 30 | ### Flashing 31 | 32 | Connect your ST-Link debugger and run: 33 | ```bash 34 | cargo run --release 35 | ``` 36 | 37 | ## Examples 38 | 39 | This template includes several examples demonstrating common embedded peripherals: 40 | 41 | ### Available Examples 42 | 43 | - **i2c** - I2C communication and device scanning (PB8/PB9) 44 | - **who_am_i** - Get device id through I2C (PB8/PB9) 45 | 46 | ### Running Examples 47 | 48 | ```bash 49 | # Run the main (blinking led) 50 | cargo run 51 | 52 | # Run the I2C example 53 | cargo run --example i2c --release 54 | 55 | {%- if sensor == "no_sensor" %} 56 | 57 | {%- else %} 58 | # Run WhoAmI example 59 | cargo run --example who_am_i --release 60 | 61 | {%- endif %} 62 | ``` 63 | 64 | ### Example Pin Assignments 65 | 66 | | Example | Pins Used | Description | 67 | |---------|-----------|-------------| 68 | | main | PA5 | Onboard LED (most STM32 boards) | 69 | | i2c | PB8 (SCL), PB9 (SDA) | I2C1 interface | 70 | | who_am_i| PB8 (SCL), PB9 (SDA) | I2C1 interface | 71 | 72 | ## Project Structure 73 | 74 | {%- if framework == "stm32rs" %} 75 | This project uses the STM32 HAL (Hardware Abstraction Layer) which provides: 76 | - Direct register access with type safety 77 | - Blocking APIs with explicit error handling 78 | - Fine-grained control over peripherals 79 | - Lower memory overhead 80 | 81 | ### Key Files 82 | 83 | - `src/main.rs` - Main application code 84 | - `memory.x` - Linker script defining memory layout 85 | - `.cargo/config.toml` - Cargo configuration for embedded target 86 | 87 | {%- else %} 88 | This project uses Embassy, an async embedded framework that provides: 89 | - Async/await support for embedded programming 90 | - Built-in drivers for many peripherals 91 | - Efficient task scheduling 92 | - Modern Rust patterns 93 | 94 | ### Key Files 95 | 96 | - `src/main.rs` - Main application code with async main 97 | - `defmt.toml` - Logging configuration 98 | - `.cargo/config.toml` - Cargo configuration for embedded target 99 | 100 | ### Logging 101 | 102 | This project uses `defmt` for efficient logging. Logs will appear in your probe-rs terminal when running the application. 103 | 104 | {%- endif %} 105 | 106 | ## Development 107 | 108 | ### Debugging 109 | 110 | You can use probe-rs for debugging: 111 | ```bash 112 | probe-rs debug --chip {{mcu}} 113 | ``` 114 | 115 | Or use GDB with your preferred setup. 116 | 117 | ### Memory Usage 118 | 119 | Check memory usage with: 120 | ```bash 121 | cargo size --release 122 | ``` 123 | 124 | ## License 125 | 126 | This project is licensed under your preferred license. 127 | -------------------------------------------------------------------------------- /util/st-template/examples/who_am_i.rs: -------------------------------------------------------------------------------- 1 | //! Who_am_i example 2 | //! 3 | //! This example demonstrates how to read the WHO_AM_I register using the {{sensor}} driver. 4 | //! 5 | //! The reference board used is the NUCLEO-F401RE; other boards may require 6 | //! setting the pins accordingly. 7 | //! 8 | //! Default pins: PB8 (SCL), PB9 (SDA) 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | {% if framework == "stm32rs" -%} 14 | use {defmt_rtt as _, panic_probe as _}; 15 | use cortex_m_rt::entry; 16 | use stm32f4xx_hal::{ 17 | i2c::I2c, 18 | pac, 19 | prelude::*, 20 | }; 21 | use defmt::*; 22 | 23 | use {{sensor}}_rs::*; 24 | 25 | #[entry] 26 | fn main() -> ! { 27 | let cp = cortex_m::Peripherals::take().unwrap(); 28 | let dp = pac::Peripherals::take().unwrap(); 29 | 30 | let rcc = dp.RCC.constrain(); 31 | let clocks = rcc.cfgr.use_hse(8.MHz()).sysclk(48.MHz()).freeze(); 32 | 33 | let gpiob = dp.GPIOB.split(); 34 | 35 | // Configure I2C pins 36 | let scl = gpiob.pb8.into_alternate().set_open_drain(); 37 | let sda = gpiob.pb9.into_alternate().set_open_drain(); 38 | 39 | // Create I2C interface 40 | let i2c = I2c::new(dp.I2C1, (scl, sda), 400.kHz(), &clocks); 41 | 42 | // Wait a boot time 43 | let mut delay = cp.SYST.delay(&clocks); 44 | delay.delay_ms(5); 45 | 46 | let mut sensor = {{sensor_capitalized}}::new_i2c(i2c, {{address}}{{delay}}); 47 | 48 | // Check device ID 49 | let id: u8 = sensor.{{who_am_i_fn}}.unwrap().into(); 50 | if id != {{id}} { 51 | info!("Unexpected device ID: {}", id); 52 | } else { 53 | info!("Sensor found succesfully..."); 54 | } 55 | 56 | loop {} 57 | } 58 | {% endif -%} 59 | 60 | {% if framework == "embassy" -%} 61 | use defmt::*; 62 | use cortex_m::prelude::*; 63 | use embassy_executor::Spawner; 64 | use embassy_stm32::{ 65 | i2c::{self, I2c, Config as I2cConfig}, 66 | time::khz, 67 | peripherals, 68 | dma::NoDma, 69 | bind_interrupts 70 | }; 71 | use embassy_time::Delay; 72 | use {defmt_rtt as _, panic_halt as _}; 73 | use {{sensor}}_rs::*; 74 | 75 | #[defmt::panic_handler] 76 | fn panic() -> ! { 77 | core::panic!("panic via `defmt::panic!`") 78 | } 79 | 80 | bind_interrupts!(struct Irqs { 81 | I2C1_EV => i2c::EventInterruptHandler; 82 | I2C1_ER => i2c::ErrorInterruptHandler; 83 | }); 84 | 85 | #[embassy_executor::main] 86 | async fn main(_spawner: Spawner) { 87 | let p = embassy_stm32::init(Default::default()); 88 | 89 | let i2c: I2c<_> = I2c::new( 90 | p.I2C1, 91 | p.PB8, 92 | p.PB9, 93 | Irqs, 94 | NoDma, 95 | NoDma, 96 | khz(100), 97 | I2cConfig::default(), 98 | ); 99 | 100 | // Wait a boot time 101 | let mut delay = Delay; 102 | delay.delay_ms(5_u32); 103 | 104 | let mut sensor = {{sensor_capitalized}}::new_i2c(i2c, {{address}}{{delay}}); 105 | 106 | // Check device ID 107 | let id: u8 = sensor.{{who_am_i_fn}}.unwrap().into(); 108 | if id != {{id}} { 109 | info!("Error: ID Sensor don't match expected!"); 110 | loop {} 111 | } else { 112 | info!("Sensor found succesfully..."); 113 | } 114 | 115 | loop {} 116 | } 117 | 118 | {% endif -%} 119 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/attributes/struct_register.rs: -------------------------------------------------------------------------------- 1 | use syn::Field; 2 | use quote::format_ident; 3 | use proc_macro2::TokenStream; 4 | use syn::{ 5 | Type, TypePath, Ident, 6 | }; 7 | 8 | use quote::quote; 9 | 10 | pub(crate) struct StructRegisterAttr {} 11 | 12 | impl StructRegisterAttr { 13 | pub fn size_of_type(ty: &Type) -> Option { 14 | if let Type::Path(TypePath { qself: None, path }) = ty { 15 | if path.segments.len() == 1 { 16 | let ident = &path.segments[0].ident; 17 | return match ident.to_string().as_str() { 18 | "u8" | "i8" => Some(1), 19 | "u16" | "i16" => Some(2), 20 | "u32" | "i32" => Some(4), 21 | "u64" | "i64" => Some(8), 22 | "u128" | "i128" => Some(16), 23 | _ => None, 24 | }; 25 | } 26 | } 27 | None 28 | } 29 | 30 | pub fn generate_to_le_bytes(fields: &[Field], buffer_size: &TokenStream) -> TokenStream { 31 | // Collect (field_name, size) pairs 32 | let names_sizes: Vec<(Ident, usize)> = fields.iter().map(|field| { 33 | let name = field.ident.clone().expect("Named field should have name"); 34 | let size = Self::size_of_type(&field.ty).expect("Only primitive types are allowed in named structs"); 35 | (name, size) 36 | }).collect(); 37 | 38 | // Generate let bindings: let _field = self.field.to_le_bytes(); 39 | let let_bindings = names_sizes.iter().map(|(name, _size)| { 40 | let tmp_name = format_ident!("_{}", name); 41 | quote! { 42 | let #tmp_name = self.#name.to_le_bytes(); 43 | } 44 | }); 45 | 46 | // Generate the concatenated byte array by flattening all bytes from each _field 47 | // For each field, generate _field[0], _field[1], ..., _field[size-1] 48 | let expanded_bytes = names_sizes.iter().flat_map(|(name, size)| { 49 | let tmp_name = format_ident!("_{}", name); 50 | (0..*size).map(move |i| { 51 | let index = syn::Index::from(i); 52 | quote! { #tmp_name[#index] } 53 | }) 54 | }); 55 | 56 | // Generate the final to_le_bytes function 57 | quote! { 58 | pub fn to_le_bytes(&self) -> [u8; #buffer_size] { 59 | #(#let_bindings)* 60 | 61 | [ 62 | #(#expanded_bytes),* 63 | ] 64 | } 65 | } 66 | } 67 | 68 | pub fn generate_from_le_bytes(fields: &Vec, buffer_size: &proc_macro2::TokenStream) -> proc_macro2::TokenStream { 69 | let mut offset = 0usize; 70 | let indexed_fields = fields.iter().map(|field| { 71 | let name = field.ident.clone().expect("Named field should have name"); 72 | let size = Self::size_of_type(&field.ty).expect("Only primitive types are allowed in named structs"); 73 | let ty = &field.ty; 74 | 75 | let start = offset; 76 | offset += size; 77 | 78 | // Generate array of bytes: [bytes[start], bytes[start+1], ..., bytes[start+size-1]] 79 | let indices = (start..start + size).map(|i| { 80 | quote! { bytes[#i] } 81 | }); 82 | 83 | quote! { 84 | #name: <#ty>::from_le_bytes([#(#indices),*]) 85 | } 86 | }); 87 | 88 | quote! { 89 | pub fn from_le_bytes(bytes: [u8; #buffer_size]) -> Self { 90 | Self { 91 | #(#indexed_fields),* 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/attributes/register.rs: -------------------------------------------------------------------------------- 1 | use syn::punctuated::Punctuated; 2 | use syn::{ 3 | Expr, Path, Result, Token, Type, TypePath, ExprPath, Lit, 4 | parse::{Parse, ParseStream}, 5 | }; 6 | 7 | use quote::quote; 8 | use crate::attributes::Order; 9 | use crate::generator::QuoteOutput; 10 | 11 | pub(crate) struct RegisterAttr { 12 | pub address: Path, 13 | pub access_type: Path, 14 | pub init_fn: Option, 15 | pub override_type: Option, 16 | pub generics_num: u8, 17 | pub order: Order 18 | } 19 | 20 | impl Parse for RegisterAttr { 21 | fn parse(input: ParseStream) -> Result { 22 | let mut access_type = None; 23 | let mut address = None; 24 | let mut init_fn = None; 25 | let mut override_type = None; 26 | let mut order = Order::Forward; 27 | let mut generics_num = None; 28 | 29 | // Parse comma-separated key-value pairs 30 | let pairs = Punctuated::::parse_terminated(input)?; 31 | 32 | for pair in pairs { 33 | let ident = pair.path.get_ident().unwrap().to_string(); 34 | match ident.as_str() { 35 | "address" => { 36 | if let Expr::Path(path) = &pair.value { 37 | address = Some(path.path.clone()); 38 | } 39 | } 40 | "access_type" => { 41 | if let Expr::Path(path) = &pair.value { 42 | access_type = Some(path.path.clone()); 43 | } 44 | }, 45 | "generics" => { 46 | if let Expr::Lit(expr_lit) = &pair.value { 47 | if let Lit::Int(lit_int) = &expr_lit.lit { 48 | generics_num = lit_int.base10_parse::().ok(); 49 | } 50 | } 51 | }, 52 | "init_fn" => { 53 | if let Expr::Path(path) = &pair.value { 54 | init_fn = Some(path.path.clone()); 55 | } 56 | }, 57 | "override_type" => { 58 | if let Expr::Path(path) = &pair.value { 59 | override_type = Some(expr_path_to_type(&path)); 60 | } 61 | }, 62 | "order" => { 63 | if let Expr::Path(path) = &pair.value { 64 | if path.path.segments[0].ident == "Inverse" { 65 | order = Order::Inverse; 66 | } else { 67 | println!("`{}` is not valid for the order attribute", path.path.segments[0].ident); 68 | } 69 | } 70 | } 71 | _ => {} 72 | } 73 | } 74 | 75 | let access_type = access_type.ok_or_else(|| input.error("missing 'state' argument"))?; 76 | let address = address.ok_or_else(|| input.error("missing 'address' argument"))?; 77 | let generics_num = generics_num.ok_or_else(|| input.error("missing 'generic' argument"))?; 78 | 79 | Ok(RegisterAttr { 80 | access_type, 81 | address, 82 | init_fn, 83 | override_type, 84 | generics_num, 85 | order 86 | }) 87 | } 88 | } 89 | 90 | fn expr_path_to_type(expr: &ExprPath) -> Type { 91 | let type_path = TypePath { 92 | qself: None, 93 | path: expr.path.clone(), 94 | }; 95 | Type::Path(type_path) 96 | } 97 | 98 | impl QuoteOutput for RegisterAttr { 99 | fn quote_read(&self) -> proc_macro2::TokenStream { 100 | let address = &self.address; 101 | quote! { sensor.read_from_register(#address as u8, buff) } 102 | } 103 | 104 | fn quote_write_single(&self) -> proc_macro2::TokenStream { 105 | let address = &self.address; 106 | quote! { sensor.write_to_register(#address as u8, &[self.0]) } 107 | } 108 | 109 | fn quote_write_multi(&self) -> proc_macro2::TokenStream { 110 | let address = &self.address; 111 | let to_fn = self.order.to_x_bytes_word(); 112 | quote! { sensor.write_to_register(#address as u8, &self.0.#to_fn()) } 113 | } 114 | 115 | fn quote_write_to_buff(&self) -> proc_macro2::TokenStream { 116 | let address = &self.address; 117 | quote! { sensor.write_to_register(#address as u8, &buff) } 118 | } 119 | 120 | fn get_access_type(&self) -> &Path { 121 | &self.access_type 122 | } 123 | 124 | fn get_init(&self) -> proc_macro2::TokenStream { 125 | if let Some(init_fn) = &self.init_fn { 126 | quote! { #init_fn() } 127 | } else { 128 | quote! { 0 } 129 | } 130 | } 131 | 132 | fn get_override_type(&self) -> Option { 133 | self.override_type.clone() 134 | } 135 | 136 | fn get_order(&self) -> Order { 137 | self.order 138 | } 139 | 140 | fn get_generics_num(&self) -> u8 { 141 | self.generics_num 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/parser.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{ 3 | parse::Parser, punctuated::Punctuated, Attribute, Expr, Fields, ItemStruct, Meta, Token, Type 4 | }; 5 | 6 | pub fn extract_value_from_attribute(attr: &Attribute) -> Option { 7 | let mut value = None; 8 | let meta = &attr.meta; 9 | 10 | if let Meta::List(meta_list) = meta { 11 | let parser = Punctuated::::parse_terminated; 12 | 13 | if let Ok(exprs) = parser.parse2(meta_list.tokens.clone()) { 14 | for expr in exprs.iter() { 15 | if let Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(lit_int), .. }) = expr { 16 | value = lit_int.base10_parse::().ok(); 17 | } 18 | } 19 | } 20 | } 21 | 22 | value 23 | } 24 | 25 | 26 | /// Analize the struct and summarize information about primitive types and 27 | /// when used as array. Extract also information about `padding_before` and 28 | /// `padding_after` 29 | /// 30 | /// # Returns 31 | /// 32 | /// * `Type`: it could be a primitive type or complex type such array 33 | /// * `array_size`: If the struct is defined as array otherwise returns 1 34 | /// * `padding_before`: number of bytes to shift the inner content 35 | /// * `padding_after`: number of bytes to shift the inner content 36 | pub fn get_type_and_array_size(item: &mut ItemStruct) -> (Type, usize, u8, u8) { 37 | 38 | match &mut item.fields { 39 | Fields::Unnamed(fields_unnamed) if fields_unnamed.unnamed.len() == 1 => { 40 | // Expect type like [DataType; N] 41 | if let Type::Array(type_array) = &fields_unnamed.unnamed[0].ty { 42 | let elem_type = &*type_array.elem; 43 | let len_expr = &type_array.len; 44 | 45 | // Try to parse length as usize literal 46 | let n_array = if let syn::Expr::Lit(expr_lit) = len_expr { 47 | if let syn::Lit::Int(lit_int) = &expr_lit.lit { 48 | lit_int.base10_parse::().unwrap_or(0) 49 | } else { 50 | 0 51 | } 52 | } else { 53 | 0 54 | }; 55 | 56 | (elem_type.clone(), n_array, 0, 0) 57 | } else { 58 | // Not an array type 59 | (fields_unnamed.unnamed[0].ty.clone(), 1, 0, 0) 60 | } 61 | }, 62 | Fields::Named(fields_named) => { 63 | // Handle the bitfield-structs 64 | let mut total_bits: u8 = 0; 65 | let mut offset_before: u8 = 0; 66 | let mut offset_after: u8 = 0; 67 | 68 | for field in &mut fields_named.named { 69 | 70 | let mut bits_value = None; 71 | 72 | // find the offset_before and offset_after tag 73 | for attr in &field.attrs { 74 | if attr.path().is_ident("bits") { 75 | bits_value = extract_value_from_attribute(attr); 76 | } else if attr.path().is_ident("offset_before") { 77 | offset_before = extract_value_from_attribute(attr).unwrap_or(0) as u8; 78 | } else if attr.path().is_ident("offset_after") { 79 | offset_after = extract_value_from_attribute(attr).unwrap_or(0) as u8; 80 | } 81 | } 82 | 83 | // remove the offset_before and offset_after tag 84 | let bits_value = bits_value.unwrap_or(0) as u8; 85 | field.attrs.retain(|attr| !attr.path().is_ident("offset_after")); 86 | field.attrs.retain(|attr| !attr.path().is_ident("offset_before")); 87 | 88 | if offset_after % 8 != 0 || offset_before % 8 != 0 { 89 | panic!("offset should have bits multiple of bytes"); 90 | } 91 | 92 | total_bits += bits_value; 93 | } 94 | 95 | 96 | let reg_type = match total_bits { 97 | 8 => "u8", 98 | 16 => "u16", 99 | 32 => "u32", 100 | _ => "u8" 101 | }; 102 | 103 | (syn::parse_str(reg_type).unwrap(), 1, offset_before / 8, offset_after / 8) 104 | } 105 | _ => { 106 | // For other struct types, fallback 107 | (syn::parse_str("u8").unwrap(), 1, 0, 0) 108 | } 109 | } 110 | } 111 | 112 | pub(crate) enum Lifetime { 113 | A, 114 | Anonym, 115 | None 116 | } 117 | 118 | 119 | /// It creates the generics part based on the input 120 | pub fn generate_generics(lifetime: Lifetime, generics_num: u8) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { 121 | 122 | let generics_str = match lifetime { 123 | Lifetime::A => quote! { <'a, }, 124 | Lifetime::Anonym => quote! { <'_, }, 125 | Lifetime::None => quote! { < }, 126 | }; 127 | 128 | let (timer_short, timer_long) = if generics_num == 2 { 129 | (quote! { T }, quote! { T: DelayNs }) 130 | } else { 131 | (quote! {}, quote! {}) 132 | }; 133 | 134 | let gen_short = quote! {#generics_str B, #timer_short >}; 135 | let gen_long = quote! {#generics_str B: BusOperation, #timer_long >}; 136 | 137 | (gen_long, gen_short) 138 | } 139 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | mod attributes; 4 | mod generator; 5 | mod parser; 6 | 7 | use proc_macro::TokenStream; 8 | use quote::quote; 9 | use syn::{ 10 | parse_macro_input, DeriveInput, Fields, ItemStruct, Path, PathSegment, Type, TypePath, Ident 11 | }; 12 | use generator::{Quote, QuoteOutput}; 13 | use attributes::{adv_register::AdvRegisterAttr, mem_bank::{MemBank, MemBankArgs}, named_register::NamedRegisterAttr, register::RegisterAttr, struct_register::StructRegisterAttr}; 14 | use parser::*; 15 | 16 | 17 | 18 | #[proc_macro_derive(MultiRegister, attributes(struct_register_attr))] 19 | pub fn multi_register_derive(input: TokenStream) -> TokenStream { 20 | // Parse the input tokens into a syntax tree 21 | let input = parse_macro_input!(input as DeriveInput); 22 | let struct_name = &input.ident; 23 | 24 | let fields = match &input.data { 25 | syn::Data::Struct(data_struct) => match &data_struct.fields { 26 | Fields::Named(fields_named) => fields_named.named.iter().cloned().collect::>(), 27 | _ => { 28 | return syn::Error::new_spanned( 29 | &input, 30 | "MultiRegister derive only supports structs with named fields", 31 | ) 32 | .to_compile_error() 33 | .into(); 34 | } 35 | }, 36 | _ => { 37 | return syn::Error::new_spanned( 38 | &input, 39 | "MultiRegister derive only supports structs", 40 | ) 41 | .to_compile_error() 42 | .into(); 43 | } 44 | }; 45 | 46 | let type_path = TypePath { 47 | qself: None, 48 | path: Path { 49 | leading_colon: None, 50 | segments: std::iter::once(PathSegment::from(struct_name.clone())).collect(), 51 | }, 52 | }; 53 | let data_type = Type::Path(type_path); 54 | 55 | let buffer_size = quote! { core::mem::size_of::<#data_type>() }; 56 | 57 | let to_le_bytes = StructRegisterAttr::generate_to_le_bytes(&fields, &buffer_size); 58 | let from_le_bytes = StructRegisterAttr::generate_from_le_bytes(&fields, &buffer_size); 59 | 60 | let expanded = quote! { 61 | impl #struct_name { 62 | #to_le_bytes 63 | #from_le_bytes 64 | } 65 | }; 66 | 67 | TokenStream::from(expanded) 68 | } 69 | 70 | 71 | #[proc_macro_attribute] 72 | pub fn adv_register(attr: TokenStream, item: TokenStream) -> TokenStream { 73 | let mut input = parse_macro_input!(item as ItemStruct); 74 | let args = parse_macro_input!(attr as AdvRegisterAttr); 75 | 76 | let (data_type, n_array, offset_before, offset_after) = get_type_and_array_size(&mut input); 77 | 78 | let generics = generate_generics(Lifetime::None, args.get_generics_num()); 79 | 80 | let mut quote = Quote::new( 81 | &input, 82 | args, 83 | &generics, 84 | &data_type, 85 | n_array, 86 | false, 87 | offset_before, 88 | offset_after 89 | ); 90 | 91 | let expanded = quote.generate(); 92 | 93 | TokenStream::from(expanded) 94 | } 95 | 96 | #[proc_macro_attribute] 97 | pub fn register(attr: TokenStream, item: TokenStream) -> TokenStream { 98 | let mut input = parse_macro_input!(item as ItemStruct); 99 | let args = parse_macro_input!(attr as RegisterAttr); 100 | 101 | let (data_type, n_array, offset_before, offset_after) = get_type_and_array_size(&mut input); 102 | let generics = generate_generics(Lifetime::None, args.get_generics_num()); 103 | 104 | let mut quote = Quote::new( 105 | &input, 106 | args, 107 | &generics, 108 | &data_type, 109 | n_array, 110 | false, 111 | offset_before, 112 | offset_after 113 | ); 114 | 115 | let expanded = quote.generate(); 116 | 117 | TokenStream::from(expanded) 118 | } 119 | 120 | #[proc_macro_attribute] 121 | pub fn named_register(attr: TokenStream, item: TokenStream) -> TokenStream { 122 | let item2 = item.clone(); 123 | let input = parse_macro_input!(item2 as ItemStruct); 124 | let args = parse_macro_input!(attr as NamedRegisterAttr); 125 | 126 | let mut bytes: usize = 0; 127 | let to_from_le_bytes: proc_macro2::TokenStream = NamedRegisterAttr::create_to_from_le_bytes(item, &mut bytes).into(); 128 | 129 | 130 | let struct_ident: &Ident = &input.ident; 131 | let path_segment = PathSegment::from(struct_ident.clone()); 132 | let path = Path { 133 | leading_colon: None, 134 | segments: std::iter::once(path_segment).collect(), 135 | }; 136 | let type_path = TypePath { qself: None, path }; 137 | let data_type = Type::Path(type_path); 138 | 139 | 140 | let generics = generate_generics(Lifetime::None, args.get_generics_num()); 141 | let mut quote = Quote::new( 142 | &input, 143 | args, 144 | &generics, 145 | &data_type, 146 | 1, 147 | true, 148 | 0, 149 | 0 150 | ); 151 | 152 | quote.override_buffer_size(bytes); 153 | 154 | let expanded = quote.generate(); 155 | 156 | TokenStream::from(quote! { 157 | #expanded 158 | #to_from_le_bytes 159 | }) 160 | } 161 | 162 | #[proc_macro_attribute] 163 | pub fn mem_bank(attr: TokenStream, item: TokenStream) -> TokenStream { 164 | let mut input = parse_macro_input!(item as MemBank); 165 | let sensor = parse_macro_input!(attr as MemBankArgs); 166 | 167 | TokenStream::from(input.create_output(&sensor)) 168 | } 169 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/attributes/adv_register.rs: -------------------------------------------------------------------------------- 1 | use syn::punctuated::Punctuated; 2 | use syn::{ 3 | Expr, Path, Result, Token, Type, TypePath, ExprPath, Lit, 4 | parse::{Parse, ParseStream}, 5 | }; 6 | 7 | use quote::quote; 8 | use crate::generator::QuoteOutput; 9 | 10 | use super::Order; 11 | 12 | pub(crate) struct AdvRegisterAttr { 13 | pub base_address: Path, 14 | pub address: Path, 15 | pub access_type: Path, 16 | pub init_fn: Option, 17 | pub override_type: Option, 18 | pub generics_num: u8, 19 | pub order: Order 20 | } 21 | 22 | impl Parse for AdvRegisterAttr { 23 | fn parse(input: ParseStream) -> Result { 24 | let mut access_type = None; 25 | let mut address = None; 26 | let mut base_address = None; 27 | let mut init_fn = None; 28 | let mut override_type = None; 29 | let mut order = Order::Forward; 30 | let mut generics_num = None; 31 | 32 | // Parse comma-separated key-value pairs 33 | let pairs = Punctuated::::parse_terminated(input)?; 34 | 35 | for pair in pairs { 36 | let ident = pair.path.get_ident().unwrap().to_string(); 37 | match ident.as_str() { 38 | "base_address" => { 39 | if let Expr::Path(path) = &pair.value { 40 | base_address = Some(path.path.clone()); 41 | } 42 | } 43 | "address" => { 44 | if let Expr::Path(path) = &pair.value { 45 | address = Some(path.path.clone()); 46 | } 47 | }, 48 | "generics" => { 49 | if let Expr::Lit(expr_lit) = &pair.value { 50 | if let Lit::Int(lit_int) = &expr_lit.lit { 51 | generics_num = lit_int.base10_parse::().ok(); 52 | } 53 | } 54 | }, 55 | "access_type" => { 56 | if let Expr::Path(path) = &pair.value { 57 | access_type = Some(path.path.clone()); 58 | } 59 | }, 60 | "init_fn" => { 61 | if let Expr::Path(path) = &pair.value { 62 | init_fn = Some(path.path.clone()); 63 | } 64 | }, 65 | "override_type" => { 66 | if let Expr::Path(path) = &pair.value { 67 | override_type = Some(expr_path_to_type(&path)); 68 | } 69 | }, 70 | "order" => { 71 | if let Expr::Path(path) = &pair.value { 72 | if path.path.segments[0].ident == "Inverse" { 73 | order = Order::Inverse; 74 | } else { 75 | println!("`{}` is not valid for the order attribute", path.path.segments[0].ident); 76 | } 77 | } 78 | } 79 | _ => {} 80 | } 81 | } 82 | 83 | let access_type = access_type.ok_or_else(|| input.error("missing 'state' argument"))?; 84 | let address = address.ok_or_else(|| input.error("missing 'address' argument"))?; 85 | let base_address = base_address.ok_or_else(|| input.error("missing 'base_address' argument"))?; 86 | let generics_num = generics_num.ok_or_else(|| input.error("missing 'generic' argument"))?; 87 | 88 | Ok(AdvRegisterAttr { 89 | base_address, 90 | access_type, 91 | address, 92 | init_fn, 93 | override_type, 94 | generics_num, 95 | order 96 | }) 97 | } 98 | } 99 | 100 | fn expr_path_to_type(expr: &ExprPath) -> Type { 101 | let type_path = TypePath { 102 | qself: None, 103 | path: expr.path.clone(), 104 | }; 105 | Type::Path(type_path) 106 | } 107 | 108 | impl QuoteOutput for AdvRegisterAttr { 109 | fn quote_read(&self) -> proc_macro2::TokenStream { 110 | let address = &self.address; 111 | let base_address = &self.base_address; 112 | quote! { sensor.ln_pg_read(#base_address as u16 + #address as u16, buff, buff.len() as u8) } 113 | } 114 | 115 | fn quote_write_single(&self) -> proc_macro2::TokenStream { 116 | let address = &self.address; 117 | let base_address = &self.base_address; 118 | quote! { sensor.ln_pg_write(#base_address as u16 + #address as u16, &[self.0], 1) } 119 | } 120 | 121 | fn quote_write_multi(&self) -> proc_macro2::TokenStream { 122 | let address = &self.address; 123 | let base_address = &self.base_address; 124 | 125 | let to_fn = self.order.to_x_bytes_word(); 126 | 127 | quote! { 128 | let output = self.0.#to_fn(); 129 | sensor.ln_pg_write(#base_address as u16 + #address as u16, &output, output.len() as u8) 130 | } 131 | } 132 | 133 | fn quote_write_to_buff(&self) -> proc_macro2::TokenStream { 134 | let address = &self.address; 135 | let base_address = &self.base_address; 136 | quote! { sensor.ln_pg_write(#base_address as u16 + #address as u16, &buff, buff.len() as u8) } 137 | } 138 | 139 | fn get_access_type(&self) -> &Path { 140 | &self.access_type 141 | } 142 | 143 | fn get_init(&self) -> proc_macro2::TokenStream { 144 | if let Some(init_fn) = &self.init_fn { 145 | quote! { #init_fn() } 146 | } else { 147 | quote! { 0 } 148 | } 149 | } 150 | 151 | fn get_override_type(&self) -> Option { 152 | self.override_type.clone() 153 | } 154 | 155 | fn get_order(&self) -> Order { 156 | self.order 157 | } 158 | 159 | fn get_generics_num(&self) -> u8 { 160 | self.generics_num 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /util/st-mems-bus/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | use core::fmt::Debug; 3 | use core::cell::RefCell; 4 | use embedded_hal::delay::DelayNs; 5 | use embedded_hal::i2c::I2c; 6 | 7 | #[cfg(feature = "i2c")] 8 | pub mod i2c; 9 | #[cfg(feature = "spi")] 10 | pub mod spi; 11 | 12 | const CHUNK_SIZE: usize = 256; 13 | 14 | pub trait BusOperation { 15 | type Error: Debug; 16 | 17 | fn read_bytes(&mut self, rbuf: &mut [u8]) -> Result<(), Self::Error>; 18 | fn write_bytes(&mut self, wbuf: &[u8]) -> Result<(), Self::Error>; 19 | fn write_byte_read_bytes(&mut self, wbuf: &[u8; 1], rbuf: &mut [u8])-> Result<(), Self::Error>; 20 | 21 | #[inline] 22 | fn read_from_register(&mut self, reg: u8, buf: &mut [u8]) -> Result<(), Self::Error> { 23 | self.write_byte_read_bytes(&[reg], buf) 24 | } 25 | #[inline] 26 | fn write_to_register(&mut self, reg: u8, buf: &[u8]) -> Result<(), Self::Error> { 27 | let mut tmp: [u8; CHUNK_SIZE + 1] = [0; CHUNK_SIZE + 1]; 28 | let mut reg = reg; 29 | for chunk in buf.chunks(CHUNK_SIZE) { 30 | tmp[0] = reg; 31 | tmp[1..1 + chunk.len()].copy_from_slice(chunk); 32 | self.write_bytes(&tmp[..1 + chunk.len()])?; 33 | 34 | reg = reg.wrapping_add(chunk.len() as u8); 35 | } 36 | Ok(()) 37 | } 38 | } 39 | 40 | pub trait MemBankFunctions { 41 | type Error; 42 | 43 | fn mem_bank_set(&mut self, val: M) -> Result<(), Self::Error>; 44 | fn mem_bank_get(&mut self) -> Result; 45 | } 46 | 47 | pub trait EmbAdvFunctions { 48 | type Error; 49 | 50 | fn ln_pg_write( 51 | &mut self, 52 | address: u16, 53 | buf: &[u8], 54 | len: u8, 55 | ) -> Result<(), Self::Error>; 56 | 57 | fn ln_pg_read( 58 | &mut self, 59 | address: u16, 60 | buf: &mut [u8], 61 | len: u8, 62 | ) -> Result<(), Self::Error>; 63 | 64 | } 65 | 66 | pub struct Owned

{ 67 | pub value: P 68 | } 69 | 70 | impl

Owned

{ 71 | pub fn new(value: P) -> Self { 72 | Self { value } 73 | } 74 | } 75 | 76 | pub struct Shared<'a, P> { 77 | pub value: &'a RefCell

78 | } 79 | 80 | impl<'a, P> Shared<'a, P> { 81 | pub fn new(value: &'a RefCell

) -> Self { 82 | Self { value } 83 | } 84 | } 85 | 86 | impl

BusOperation for Owned

where P: BusOperation { 87 | type Error = P::Error; 88 | 89 | /// Reads bytes from the bus. 90 | /// 91 | /// # Arguments 92 | /// 93 | /// * `rbuf`: Buffer to store the read bytes. 94 | /// 95 | /// # Returns 96 | /// 97 | /// * `Result` 98 | /// * `()` 99 | /// * `Err`: Returns an error if the read operation fails. 100 | #[inline] 101 | fn read_bytes(&mut self, rbuf: &mut [u8]) -> Result<(), Self::Error> { 102 | self.value.read_bytes(rbuf) 103 | } 104 | 105 | /// Writes bytes to the bus. 106 | /// 107 | /// # Arguments 108 | /// 109 | /// * `wbuf`: Buffer containing the bytes to write. 110 | /// 111 | /// # Returns 112 | /// 113 | /// * `Result` 114 | /// * `()` 115 | /// * `Err`: Returns an error if the write operation fails. 116 | #[inline] 117 | fn write_bytes(&mut self, wbuf: &[u8]) -> Result<(), Self::Error> { 118 | self.value.write_bytes(wbuf) 119 | } 120 | 121 | /// Writes a byte and then reads bytes from the bus. 122 | /// 123 | /// # Arguments 124 | /// 125 | /// * `wbuf`: Buffer containing the byte to write. 126 | /// * `rbuf`: Buffer to store the read bytes. 127 | /// 128 | /// # Returns 129 | /// 130 | /// * `Result` 131 | /// * `()` 132 | /// * `Err`: Returns an error if the write-read operation fails. 133 | #[inline] 134 | fn write_byte_read_bytes(&mut self, wbuf: &[u8; 1], rbuf: &mut [u8])-> Result<(), Self::Error> { 135 | self.value.write_byte_read_bytes(wbuf, rbuf) 136 | } 137 | } 138 | 139 | impl<'a, P> BusOperation for Shared<'a, P> where P: BusOperation { 140 | type Error = P::Error; 141 | 142 | /// Reads bytes from the SPI bus. 143 | /// 144 | /// # Arguments 145 | /// 146 | /// * `rbuf`: Buffer to store the read bytes. 147 | /// 148 | /// # Returns 149 | /// 150 | /// * `Result` 151 | /// * `()` 152 | /// * `Err`: Returns an error if the read operation fails. 153 | #[inline] 154 | fn read_bytes(&mut self, rbuf: &mut [u8]) -> Result<(), Self::Error> { 155 | self.value.borrow_mut().read_bytes(rbuf)?; 156 | 157 | Ok(()) 158 | } 159 | 160 | /// Writes bytes to the SPI bus. 161 | /// 162 | /// # Arguments 163 | /// 164 | /// * `wbuf`: Buffer containing the bytes to write. 165 | /// 166 | /// # Returns 167 | /// 168 | /// * `Result` 169 | /// * `()` 170 | /// * `Err`: Returns an error if the write operation fails. 171 | #[inline] 172 | fn write_bytes(&mut self, wbuf: &[u8]) -> Result<(), Self::Error> { 173 | self.value.borrow_mut().write_bytes(wbuf)?; 174 | Ok(()) 175 | } 176 | 177 | /// Writes a byte and then reads bytes from the SPI bus. 178 | /// 179 | /// # Arguments 180 | /// 181 | /// * `wbuf`: Buffer containing the byte to write. 182 | /// * `rbuf`: Buffer to store the read bytes. 183 | /// 184 | /// # Returns 185 | /// 186 | /// * `Result` 187 | /// * `()` 188 | /// * `Err`: Returns an error if the write-read operation fails. 189 | #[inline] 190 | fn write_byte_read_bytes( 191 | &mut self, 192 | wbuf: &[u8; 1], 193 | rbuf: &mut [u8], 194 | ) -> Result<(), Self::Error> { 195 | self.value.borrow_mut() 196 | .write_byte_read_bytes(wbuf, rbuf)?; 197 | 198 | Ok(()) 199 | } 200 | } 201 | 202 | impl<'a, P> DelayNs for Shared<'a, P> where P: DelayNs { 203 | fn delay_ms(&mut self, ms: u32) { 204 | self.value.borrow_mut().delay_ms(ms) 205 | } 206 | 207 | fn delay_ns(&mut self, ns: u32) { 208 | self.value.borrow_mut().delay_ns(ns); 209 | } 210 | 211 | fn delay_us(&mut self, us: u32) { 212 | self.value.borrow_mut().delay_us(us); 213 | } 214 | } 215 | 216 | impl<'a, P> embedded_hal::i2c::ErrorType for Shared<'a, P> 217 | where 218 | P: I2c, 219 | { 220 | type Error = P::Error; 221 | } 222 | 223 | impl<'a, P> I2c for Shared<'a, P> where P: I2c { 224 | 225 | fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { 226 | self.value.borrow_mut().read(address, read) 227 | } 228 | 229 | fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { 230 | self.value.borrow_mut().write(address, write) 231 | } 232 | 233 | fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { 234 | self.value.borrow_mut().write_read(address, write, read) 235 | } 236 | 237 | fn transaction( 238 | &mut self, 239 | address: u8, 240 | operations: &mut [embedded_hal::i2c::Operation<'_>], 241 | ) -> Result<(), Self::Error> { 242 | self.value.borrow_mut().transaction(address, operations) 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/attributes/named_register.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{punctuated::Punctuated, DeriveInput, Field}; 3 | use syn::{ 4 | Expr, Path, Result, Token, Type, TypePath, ExprPath, Lit, Fields, 5 | parse::{Parse, ParseStream}, 6 | parse_macro_input 7 | }; 8 | 9 | use crate::attributes::struct_register::StructRegisterAttr; 10 | use quote::quote; 11 | use crate::attributes::Order; 12 | use crate::generator::QuoteOutput; 13 | 14 | pub(crate) struct NamedRegisterAttr { 15 | pub address: Path, 16 | pub access_type: Path, 17 | pub init_fn: Option, 18 | pub override_type: Option, 19 | pub generics_num: u8, 20 | pub order: Order 21 | } 22 | 23 | impl Parse for NamedRegisterAttr { 24 | fn parse(input: ParseStream) -> Result { 25 | let mut access_type = None; 26 | let mut address = None; 27 | let mut init_fn = None; 28 | let mut override_type = None; 29 | let mut order = Order::Forward; 30 | let mut generics_num = None; 31 | 32 | // Parse comma-separated key-value pairs 33 | let pairs = Punctuated::::parse_terminated(input)?; 34 | 35 | for pair in pairs { 36 | let ident = pair.path.get_ident().unwrap().to_string(); 37 | match ident.as_str() { 38 | "address" => { 39 | if let Expr::Path(path) = &pair.value { 40 | address = Some(path.path.clone()); 41 | } 42 | } 43 | "access_type" => { 44 | if let Expr::Path(path) = &pair.value { 45 | access_type = Some(path.path.clone()); 46 | } 47 | }, 48 | "generics" => { 49 | if let Expr::Lit(expr_lit) = &pair.value { 50 | if let Lit::Int(lit_int) = &expr_lit.lit { 51 | generics_num = lit_int.base10_parse::().ok(); 52 | } 53 | } 54 | }, 55 | "init_fn" => { 56 | if let Expr::Path(path) = &pair.value { 57 | init_fn = Some(path.path.clone()); 58 | } 59 | }, 60 | "override_type" => { 61 | if let Expr::Path(path) = &pair.value { 62 | override_type = Some(expr_path_to_type(&path)); 63 | } 64 | }, 65 | "order" => { 66 | if let Expr::Path(path) = &pair.value { 67 | if path.path.segments[0].ident == "Inverse" { 68 | order = Order::Inverse; 69 | } else { 70 | println!("`{}` is not valid for the order attribute", path.path.segments[0].ident); 71 | } 72 | } 73 | } 74 | _ => {} 75 | } 76 | } 77 | 78 | let access_type = access_type.ok_or_else(|| input.error("missing 'state' argument"))?; 79 | let address = address.ok_or_else(|| input.error("missing 'address' argument"))?; 80 | let generics_num = generics_num.ok_or_else(|| input.error("missing 'generic' argument"))?; 81 | 82 | Ok(NamedRegisterAttr { 83 | access_type, 84 | address, 85 | init_fn, 86 | override_type, 87 | generics_num, 88 | order 89 | }) 90 | } 91 | } 92 | 93 | fn expr_path_to_type(expr: &ExprPath) -> Type { 94 | let type_path = TypePath { 95 | qself: None, 96 | path: expr.path.clone(), 97 | }; 98 | Type::Path(type_path) 99 | } 100 | 101 | impl QuoteOutput for NamedRegisterAttr { 102 | fn quote_read(&self) -> proc_macro2::TokenStream { 103 | let address = &self.address; 104 | quote! { sensor.read_from_register(#address as u8, buff) } 105 | } 106 | 107 | fn quote_write_single(&self) -> proc_macro2::TokenStream { 108 | //let address = &self.address; 109 | //quote! { sensor.write_to_register(#address as u8, &[self]) } 110 | panic!("not required"); 111 | } 112 | 113 | fn quote_write_multi(&self) -> proc_macro2::TokenStream { 114 | let address = &self.address; 115 | let to_fn = self.order.to_x_bytes_word(); 116 | quote! { sensor.write_to_register(#address as u8, &self.#to_fn()) } 117 | } 118 | 119 | fn quote_write_to_buff(&self) -> proc_macro2::TokenStream { 120 | let address = &self.address; 121 | quote! { sensor.write_to_register(#address as u8, &buff) } 122 | } 123 | 124 | fn get_access_type(&self) -> &Path { 125 | &self.access_type 126 | } 127 | 128 | fn get_init(&self) -> proc_macro2::TokenStream { 129 | if let Some(init_fn) = &self.init_fn { 130 | quote! { #init_fn() } 131 | } else { 132 | quote! { 0 } 133 | } 134 | } 135 | 136 | fn get_override_type(&self) -> Option { 137 | self.override_type.clone() 138 | } 139 | 140 | fn get_order(&self) -> Order { 141 | self.order 142 | } 143 | 144 | fn get_generics_num(&self) -> u8 { 145 | self.generics_num 146 | } 147 | } 148 | 149 | impl NamedRegisterAttr { 150 | 151 | pub fn bytes_number(fields: &Vec) -> usize { 152 | let mut total_bytes = 0; 153 | for f in fields { 154 | total_bytes += StructRegisterAttr::size_of_type(&f.ty).expect("Only primitive types are allowed"); 155 | } 156 | 157 | total_bytes 158 | } 159 | 160 | pub fn create_to_from_le_bytes(input: TokenStream, bytes_number: &mut usize) -> TokenStream { 161 | // Parse the input tokens into a syntax tree 162 | let input = parse_macro_input!(input as DeriveInput); 163 | let struct_name = &input.ident; 164 | 165 | let fields = match &input.data { 166 | syn::Data::Struct(data_struct) => match &data_struct.fields { 167 | Fields::Named(fields_named) => fields_named.named.iter().cloned().collect::>(), 168 | _ => { 169 | return syn::Error::new_spanned( 170 | &input, 171 | "named_register only supports structs with named fields", 172 | ) 173 | .to_compile_error() 174 | .into(); 175 | } 176 | }, 177 | _ => { 178 | return syn::Error::new_spanned( 179 | &input, 180 | "named_register derive only supports structs", 181 | ) 182 | .to_compile_error() 183 | .into(); 184 | } 185 | }; 186 | 187 | *bytes_number = Self::bytes_number(&fields); 188 | 189 | let to_le_bytes = StructRegisterAttr::generate_to_le_bytes(&fields, "e! { #bytes_number } ); 190 | let from_le_bytes = StructRegisterAttr::generate_from_le_bytes(&fields, "e! { #bytes_number }); 191 | 192 | 193 | let expanded = quote! { 194 | impl #struct_name { 195 | #to_le_bytes 196 | #from_le_bytes 197 | } 198 | }; 199 | 200 | TokenStream::from(expanded) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/attributes/mem_bank.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | parse::{Parse, ParseStream}, Meta, MetaList, Ident, ItemEnum, LitStr, LitInt, Result, Token, Attribute, Variant 3 | }; 4 | 5 | use quote::quote; 6 | use crate::{generate_generics, Lifetime}; 7 | 8 | pub(crate) struct MemBankArgs { 9 | device: Ident, 10 | generics: Option, 11 | } 12 | 13 | impl Parse for MemBankArgs { 14 | fn parse(input: ParseStream) -> Result { 15 | // Parse the first argument: an identifier 16 | let device: Ident = input.parse()?; 17 | 18 | let mut generics: Option = None; 19 | 20 | // If there is a comma, parse the key-value pair 21 | if input.peek(Token![,]) { 22 | input.parse::()?; 23 | 24 | // Parse the key (should be "generics") 25 | let key: Ident = input.parse()?; 26 | if key != "generics" { 27 | return Err(syn::Error::new_spanned(key, "Expected key `generics`")); 28 | } 29 | 30 | input.parse::()?; 31 | 32 | let value: LitInt = input.parse()?; 33 | generics = Some(value.base10_parse()?); 34 | } 35 | 36 | if !input.is_empty() { 37 | return Err(input.error("Unexpected tokens after attribute arguments")); 38 | } 39 | 40 | Ok(MemBankArgs { device, generics }) 41 | } 42 | } 43 | 44 | pub(crate) struct VariantAttr { 45 | pub(crate) struct_name: Ident, 46 | pub(crate) fn_name: LitStr 47 | } 48 | 49 | impl Parse for VariantAttr { 50 | fn parse(input: ParseStream) -> Result { 51 | 52 | let struct_name: Ident = input.parse()?; 53 | let mut fn_name = None; 54 | 55 | if input.peek(Token![,]) { 56 | let _comma: Token![,] = input.parse()?; 57 | 58 | let key: Ident = input.parse()?; 59 | if key != "fn_name" { 60 | return Err(syn::Error::new(key.span(), "Expected `fn_name`")); 61 | } 62 | let _eq: Token![=] = input.parse()?; 63 | let fn_name_lit: LitStr = input.parse()?; 64 | fn_name = Some(fn_name_lit); 65 | } 66 | 67 | Ok(VariantAttr { struct_name, fn_name: fn_name.unwrap() }) 68 | } 69 | } 70 | 71 | pub(crate) struct MemBank { 72 | pub enum_name: Ident, 73 | pub variants: Vec<(VariantAttr, Variant)>, 74 | pub main_variant: Variant, 75 | pub enum_raw: ItemEnum, 76 | } 77 | 78 | impl Parse for MemBank { 79 | fn parse(input: ParseStream) -> Result { 80 | let enum_obj: ItemEnum = input.parse()?; 81 | let enum_name = enum_obj.ident.clone(); 82 | 83 | let mut main = None; 84 | 85 | for variant in &enum_obj.variants { 86 | for attr in &variant.attrs { 87 | if attr.path().is_ident("main") { 88 | main = Some(variant.clone()); 89 | } 90 | } 91 | } 92 | 93 | if main.is_none() { 94 | panic!("No variant with is decorated with `#[main]`"); 95 | } 96 | 97 | let main_variant = main.unwrap(); 98 | let mut variants = vec![]; 99 | 100 | for variant in &enum_obj.variants { 101 | for attr in &variant.attrs { 102 | if let Some(variant_obj) = Self::extract_state(attr) { 103 | variants.push((variant_obj, variant.clone())); 104 | } 105 | } 106 | } 107 | 108 | Ok(MemBank { 109 | enum_name: enum_name.clone(), 110 | variants, 111 | main_variant, 112 | enum_raw: enum_obj 113 | }) 114 | } 115 | } 116 | 117 | impl MemBank { 118 | 119 | pub fn create_output(&mut self, attr: &MemBankArgs) -> proc_macro2::TokenStream { 120 | let sensor_name = &attr.device; 121 | let result: Vec = self.variants.iter().map(|(variant, variant_raw)| { 122 | let variant_name = &variant_raw.ident; 123 | self.create_state_struct(&variant, &sensor_name, variant_name, attr.generics.unwrap()) 124 | }).collect(); 125 | 126 | for variant in &mut self.enum_raw.variants { 127 | variant.attrs = Self::filter_name_attrs(&variant.attrs); 128 | } 129 | 130 | let input = &self.enum_raw; 131 | 132 | quote! { 133 | #input 134 | 135 | #(#result)* 136 | } 137 | } 138 | 139 | fn extract_state(attr: &Attribute) -> Option { 140 | if attr.path().is_ident("state") { 141 | if let Meta::List(MetaList { tokens, .. }) = &attr.meta { 142 | return syn::parse2::(tokens.clone()).ok(); 143 | } 144 | } 145 | None 146 | } 147 | 148 | fn create_state_struct(&self, variant: &VariantAttr, sensor_name: &Ident, variant_name: &Ident, generics_num: u8) -> proc_macro2::TokenStream { 149 | let name = &variant.struct_name; 150 | let fn_name = &variant.fn_name; 151 | let main_variant = &self.main_variant.ident; 152 | 153 | let (long_generics_a, _short_generics_a) = generate_generics(Lifetime::A, generics_num); 154 | let (_long_generics_anonym, short_generics_anonym) = generate_generics(Lifetime::Anonym, generics_num); 155 | let (long_generics, short_generics) = generate_generics(Lifetime::None, generics_num); 156 | 157 | let (generics_for_operate, where_clause) = if generics_num == 2 { 158 | (quote! { }, quote! { where B: BusOperation, T: DelayNs, F: FnOnce(&mut #name #short_generics) -> Result> } ) 159 | } else { 160 | (quote! { }, quote! { where B: BusOperation, F: FnOnce(&mut #name #short_generics) -> Result> } ) 161 | }; 162 | 163 | let fn_name = Ident::new(&fn_name.value(), fn_name.span()); 164 | let enum_name = &self.enum_name; 165 | 166 | quote!( 167 | 168 | pub struct #name #long_generics_a { 169 | sensor: &'a mut #sensor_name #short_generics 170 | } 171 | 172 | impl #long_generics #name #short_generics_anonym { 173 | pub fn write_to_register(&mut self, reg: u8, buf: &[u8]) -> Result<(), Error> { 174 | self.sensor.write_to_register(reg, buf) 175 | } 176 | 177 | 178 | pub fn read_from_register(&mut self, reg: u8, buf: &mut [u8]) -> Result<(), Error> { 179 | self.sensor.read_from_register(reg, buf) 180 | } 181 | } 182 | 183 | impl #enum_name { 184 | 185 | pub fn #fn_name #generics_for_operate (sensor: &mut #sensor_name #short_generics, f: F) -> Result> #where_clause { 186 | 187 | sensor.mem_bank_set(Self::#variant_name)?; 188 | let mut state = #name { sensor }; 189 | let result = f(&mut state); 190 | sensor.mem_bank_set(Self::#main_variant)?; 191 | result 192 | 193 | } 194 | 195 | } 196 | ) 197 | 198 | } 199 | 200 | fn filter_name_attrs(attrs: &[Attribute]) -> Vec { 201 | attrs.iter() 202 | .filter(|attr| { 203 | !attr.path().is_ident("state") 204 | }) 205 | .filter(|attr| { 206 | !attr.path().is_ident("main") 207 | }) 208 | .cloned() 209 | .collect() 210 | 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /util/st-mem-bank-macro/README.md: -------------------------------------------------------------------------------- 1 | # st-mem-bank-macro 2 | [![Crates.io][crates-badge]][crates-url] 3 | [![BSD 3-Clause licensed][bsd-badge]][bsd-url] 4 | 5 | [crates-badge]: https://img.shields.io/crates/v/st-mem-bank-macro 6 | [crates-url]: https://crates.io/crates/st-mem-bank-macro 7 | [bsd-badge]: https://img.shields.io/crates/l/st-mem-bank-macro 8 | [bsd-url]: https://opensource.org/licenses/BSD-3-Clause 9 | 10 | This crate simplifies implementing memory state management for sensors. Some sensors have a register (called **MemBank**) that can change the address space, enabling access on special registers while restricting access to others during that state. Additionally, it streamlines register access over communication buses such as I2C or SPI. 11 | 12 | --- 13 | 14 | ## `mem_bank` Macro 15 | 16 | The `mem_bank` macro is applied to an enum representing the sensor's memory states. It uses annotations to provide metadata about each state. 17 | 18 | ### Variant Annotations 19 | 20 | - `#[main]`: Marks the default (main) state. 21 | - `#[state(StructName, fn_name = "...")]`: For other states, specifies: 22 | - `StructName`: A struct representing the state (used internally). 23 | - `fn_name`: A function is generate with that name, it handles the state transition via a closure. The closure moves into the specified state and then returns to the main state. 24 | 25 | ### Enum Annotation 26 | 27 | Annotate the entire enum as: 28 | 29 | ```rust 30 | #[mem_bank(SensorName, generics = N)] 31 | ``` 32 | - `SensorName`: This is a link to the struct that represent the main sensor lib. 33 | - 'generics': Number of generic parameters (1 or 2). The sensor must be compatible, typically including a `BusOperation` trait (the bus crate) and optionally a `DelayNs` trait from embedded_hal. 34 | 35 | 36 | ### Generated Code 37 | 38 | - Creates a struct holding a mutable reference to the sensor instance. 39 | - Generates functions to instantiate this struct, enabling state transitions. 40 | - Implements a state mechanism controlling register access per state. 41 | 42 | **Note**: The fn_name closure wraps all errors. If an error occurs during the clousure execution a return to the main MemBank is tried, if fails the error returned in the MemBank, it takes precedence over others. 43 | 44 | ## register macro 45 | 46 | The `register` macro works alongside the [bitfield-struct](https://crates.io/crates/bitfield-struct) crate. The sensor must implement these two functions: 47 | ```[Rust] 48 | pub fn write_to_register(&mut self, reg: u8, buf: &[u8]) -> Result<(), Error> { 49 | self.bus.write_to_register(reg, buf).map_err(Error::Bus) 50 | } 51 | 52 | pub fn read_from_register(&mut self, reg: u8, buf: &mut [u8]) -> Result<(), Error> { 53 | self.bus.read_from_register(reg, buf).map_err(Error::Bus) 54 | } 55 | ``` 56 | Suggested implementation considers an attribute (like the `bus` in the example) that is generic over the `BusOperation` trait, as it already provides default implementations for write_to_register and read_from_register. 57 | 58 | 59 | ### Usage 60 | 61 | Annotate a struct representing a register with: `#[register(address = ..., access_type = ..., generics = ...)]` 62 | All three attributes are mandatory: 63 | - address: The register address used in read/write calls. 64 | - access_type: The scope where the register is accessible: either the sensor struct or a `mem_bank` state. 65 | - generics: Must match the generics count used in the `mem_bank` macro. 66 | 67 | Example: 68 | 69 | ```[Rust] 70 | #[register(address = Reg::Ctrl, access_type = Lsm6dsv16x, generics = 2)] 71 | #[cfg_attr(feature = "bit_order_msb", bitfield(u8, order = Msb))] 72 | #[cfg_attr(not(feature = "bit_order_msb"), bitfield(u8, order = Lsb))] 73 | pub struct Ctrl { 74 | #[bits(1)] 75 | pub feature1: bool, 76 | ... 77 | } 78 | ``` 79 | 80 | Using the register in sensor code: 81 | ```[Rust] 82 | 83 | impl Lsm6dsv16x where B: BusOperation, T: DelayNs { 84 | pub fn activate_feature1(&mut self) -> Result<(), Error> { 85 | let mut reg = Ctrl::read(self)?; 86 | reg.set_feature1(true); // Generated by bitfield-struct 87 | Ctrl::write(self) 88 | } 89 | } 90 | 91 | ``` 92 | 93 | ### Additional Attributes 94 | - init_fn: Specifies a function to initialize non-primitive types. 95 | - override_type: Overrides the type used for to_le_bytes() and from_le_bytes() conversions. 96 | - order: Defines byte order when the struct spans multiple registers (e.g., use to_be_bytes instead of to_le_bytes). 97 | 98 | ### Advanced use cases 99 | 100 | Some edge uses cases are covered, for example: 101 | 102 | - Values defined on more reigsters with 3 axis: X, Y, Z axis over i16: 103 | ``` 104 | #[register(address = Reg::OutxLG, access_type = Lsm6dsv16x, generics = 2)] 105 | pub struct OutXYZG(pub [i16; 3]); 106 | ``` 107 | - Values defined on more reigsters and accessed with named fields: 108 | ``` 109 | #[named_register(address = Reg::OutxLG, access_type = Lsm6dsv16x, generics = 2)] 110 | pub struct OutXYZ { 111 | pub x: i16, 112 | pub y: i16, 113 | pub z: i16 114 | } 115 | ``` 116 | 117 | - custom struct: Each byte is converted into the custom struct. 118 | ``` 119 | #[register(address = EmbReg::FsmOuts1, access_type = EmbedFuncState, init_fn = FsmOutsElement::new, generics = 2)] 120 | pub struct FsmOut(pub [FsmOutsElement; 8]); 121 | 122 | #[register(address = EmbReg::FsmOuts1, access_type = EmbedFuncState, generics = 2)] 123 | #[cfg_attr(feature = "bit_order_msb", bitfield(u8, order = Msb))] 124 | #[cfg_attr(not(feature = "bit_order_msb"), bitfield(u8, order = Lsb))] 125 | pub struct FsmOutsElement { 126 | #[bits(1)] 127 | pub fsm_n_v: u8, 128 | ... 129 | } 130 | 131 | impl FsmOutsElement { 132 | pub fn from_le_bytes(val: [u8; 1]) -> Self { 133 | FsmOutsElement(val[0]) 134 | } 135 | 136 | pub fn to_le_bytes(&self) -> [u8; 1] { 137 | [self.0] 138 | } 139 | } 140 | ``` 141 | - bitfield over more bytes: The bitfield accepts only u16, but inner type could be signed, conversion is handled. `order = Reverse` parameter could be supplied to reverse how registers are read or write. 142 | ```[rust] 143 | #[register(address = Reg::OutXL, access_type = Lsm6dsv16x, generics = 2)] 144 | #[cfg_attr(feature = "bit_order_msb", bitfield(u16, order = Msb))] 145 | #[cfg_attr(not(feature = "bit_order_msb"), bitfield(u16, order = Lsb))] 146 | pub struct OutX { 147 | #[bits(16, access = RO)] 148 | pub outx: i16, 149 | } 150 | ``` 151 | 152 | - Last parameters are applied inside the struct. Allowing the offset for the register that provide only msb/lsb part: 153 | - offset_before(#bit_number): the conversion for multibyte type like (u16/i16/u32/i32) has an offset corresponding to the bit_number. For example having #bit_number = 8 and i16 the conversion happens as: i16::from_le_bytes([0, reg]); 154 | - offset_after(#bit_number): Do the same but apply the offset on the opposite part of the array 155 | 156 | ## adv_register 157 | This macro is equivalent to register and supports the same attributes, with the addition of a required `base_address` attribute that specifies the base page of embedded advanced functions. In this case, reads and writes are performed using ln_pg_write and ln_pg_read methods. It is recommended to implement the `EmbAdvFunctions` trait from the bus crate to support this functionality. 158 | 159 | An example is: 160 | ```[rust] 161 | #[adv_register(base_address = AdvPage::_0, address = EmbAdv0Reg::SflpGameGbiasxL, access_type = Lsm6dsv16x, generics = 2)] 162 | pub struct SflpGameGbiasXYZ(pub [u16; 3]); 163 | ``` 164 | **More information: [http://www.st.com](http://st.com/MEMS)** 165 | 166 | **Copyright © 2025 STMicroelectronics** -------------------------------------------------------------------------------- /util/st-mem-bank-macro/src/generator.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | Path, Type, ItemStruct, PathSegment, TypePath 3 | }; 4 | 5 | use quote::quote; 6 | 7 | use crate::{attributes::Order, StructRegisterAttr}; 8 | 9 | pub(crate) trait QuoteOutput { 10 | fn quote_write_single(&self) -> proc_macro2::TokenStream; 11 | fn quote_write_multi(&self) -> proc_macro2::TokenStream; 12 | fn quote_write_to_buff(&self) -> proc_macro2::TokenStream; 13 | fn get_access_type(&self) -> &Path; 14 | fn quote_read(&self) -> proc_macro2::TokenStream; 15 | fn get_init(&self) -> proc_macro2::TokenStream; 16 | fn get_override_type(&self) -> Option; 17 | fn get_order(&self) -> Order; 18 | fn get_generics_num(&self) -> u8; 19 | } 20 | 21 | pub(crate) struct Quote<'a, T> where T: QuoteOutput { 22 | input: &'a ItemStruct, 23 | args: T, 24 | generics: &'a (proc_macro2::TokenStream, proc_macro2::TokenStream), 25 | data_type: Type, 26 | pub(crate) buffer_size: proc_macro2::TokenStream, 27 | n_array: usize, 28 | use_new: bool, 29 | offset: (u8, u8) 30 | } 31 | 32 | impl<'a, T> Quote<'a, T> where T: QuoteOutput { 33 | pub fn new( 34 | input: &'a ItemStruct, 35 | args: T, 36 | generics: &'a (proc_macro2::TokenStream, proc_macro2::TokenStream), 37 | data_type: &'a Type, 38 | n_array: usize, 39 | use_new: bool, 40 | offset_before: u8, 41 | offset_after: u8 42 | ) -> Self { 43 | let mut data_type = data_type.clone(); 44 | 45 | if let Some(override_type) = args.get_override_type() { 46 | data_type = override_type; 47 | } 48 | 49 | let num_bytes = quote! { core::mem::size_of::<#data_type>() }; 50 | let buffer_size = quote! { #n_array * #num_bytes }; 51 | 52 | Self { 53 | input, 54 | args, 55 | generics, 56 | data_type: data_type.clone(), 57 | buffer_size, 58 | n_array, 59 | use_new, 60 | offset: (offset_before, offset_after) 61 | } 62 | } 63 | 64 | pub fn override_buffer_size(&mut self, size: usize) { 65 | self.buffer_size = quote! { #size }; 66 | } 67 | 68 | pub fn generate(&mut self) -> proc_macro2::TokenStream { 69 | 70 | if self.n_array > 1 { 71 | self.quote_for_array() 72 | } else if Self::is_u8(&self.data_type) { 73 | self.quote_for_u8() 74 | } else { 75 | self.quote_for_multibyte() 76 | } 77 | } 78 | 79 | fn is_u8(ty: &Type) -> bool { 80 | if let Type::Path(TypePath { path, .. }) = ty { 81 | if let Some(PathSegment { ident, .. }) = path.segments.last() { 82 | return ident == "u8"; 83 | } 84 | } 85 | false 86 | } 87 | 88 | fn quote_for_u8(&self) -> proc_macro2::TokenStream { 89 | let (long_generics, short_generics) = self.generics; 90 | let struct_name = &self.input.ident; 91 | 92 | let Quote { 93 | input, 94 | .. 95 | } = self; 96 | 97 | let read = self.args.quote_read(); 98 | let write = self.args.quote_write_single(); 99 | let access_type = self.args.get_access_type(); 100 | let init = self.args.get_init(); 101 | 102 | let create_from_buff = if self.use_new { 103 | quote! { Self::new(buff[0]) } 104 | } else { 105 | quote! { Self(buff[0]) } 106 | }; 107 | 108 | quote! { 109 | #input 110 | 111 | impl #struct_name { 112 | pub fn read #long_generics (sensor: &mut #access_type #short_generics) -> Result> { 113 | let mut buff = [#init; 1]; 114 | Self::read_more(sensor, &mut buff)?; 115 | Ok(#create_from_buff) 116 | } 117 | 118 | pub fn write #long_generics (&self, sensor: &mut #access_type #short_generics) -> Result<(), Error> { 119 | #write 120 | } 121 | 122 | #[inline] 123 | pub fn read_more #long_generics (sensor: &mut #access_type #short_generics, buff: &mut [u8]) -> Result<(), Error> { 124 | #read 125 | } 126 | } 127 | } 128 | } 129 | 130 | fn quote_for_multibyte(&self) -> proc_macro2::TokenStream { 131 | let (long_generics, short_generics) = self.generics; 132 | let struct_name = &self.input.ident; 133 | 134 | let Quote { 135 | input, 136 | data_type, 137 | buffer_size, 138 | .. 139 | } = self; 140 | 141 | let read = self.args.quote_read(); 142 | let write = self.args.quote_write_multi(); 143 | let access_type = self.args.get_access_type(); 144 | let init = self.args.get_init(); 145 | 146 | let create_from_val = if self.use_new { 147 | quote! { val } 148 | } else { 149 | quote! { Self(val) } 150 | }; 151 | 152 | let from_fn = self.args.get_order().from_x_bytes_word(); 153 | 154 | let read_more = if self.offset.0 + self.offset.1 == 0 { 155 | quote! { Self::read_more(sensor, &mut buff)?; } 156 | } else { 157 | let size = StructRegisterAttr::size_of_type(&self.data_type).expect("Cannot use offset with other than primitive types"); 158 | let offset_before = self.offset.0 as usize; 159 | let end = size - (self.offset.1 as usize); 160 | quote! { Self::read_more(sensor, &mut buff[#offset_before..#end])?; } 161 | }; 162 | 163 | quote! { 164 | #input 165 | 166 | impl #struct_name { 167 | pub fn read #long_generics (sensor: &mut #access_type #short_generics) -> Result> { 168 | let mut buff = [#init; #buffer_size]; 169 | #read_more 170 | let val = <#data_type>::#from_fn(buff); 171 | Ok(#create_from_val) 172 | } 173 | 174 | pub fn write #long_generics (&self, sensor: &mut #access_type #short_generics) -> Result<(), Error> { 175 | #write 176 | } 177 | 178 | #[inline] 179 | pub fn read_more #long_generics (sensor: &mut #access_type #short_generics, buff: &mut [u8]) -> Result<(), Error> { 180 | #read 181 | } 182 | } 183 | } 184 | } 185 | 186 | fn quote_for_array(&self) -> proc_macro2::TokenStream { 187 | let (long_generics, short_generics) = self.generics; 188 | let struct_name = &self.input.ident; 189 | 190 | let Quote { 191 | input, 192 | data_type, 193 | n_array, 194 | .. 195 | } = self; 196 | 197 | let buffer_size_str = &self.buffer_size; 198 | 199 | let num_bytes = quote! { core::mem::size_of::<#data_type>() }; 200 | let read = self.args.quote_read(); 201 | let write = self.args.quote_write_to_buff(); 202 | let access_type = self.args.get_access_type(); 203 | let init = self.args.get_init(); 204 | 205 | // a named register cannot use this function 206 | 207 | let from_fn = self.args.get_order().from_x_bytes_word(); 208 | let to_fn = self.args.get_order().to_x_bytes_word(); 209 | 210 | let type_size = StructRegisterAttr::size_of_type(&self.data_type); 211 | 212 | 213 | let type_assignment = if let Some(bytes) = type_size { 214 | let assignments = (0..*n_array).map(|i| { 215 | let bytes_str = (0..bytes).map(|j| { 216 | let idx = i * bytes + j; 217 | quote! { buff[#idx] } 218 | }); 219 | 220 | quote! { 221 | val[#i] = <#data_type>::#from_fn([#(#bytes_str),*]); 222 | } 223 | }); 224 | 225 | quote! { #(#assignments)* } 226 | } else { 227 | quote! { 228 | for i in 0..#n_array { 229 | val[i] = <#data_type>::#from_fn( 230 | buff[i * #num_bytes..(i + 1) * #num_bytes] 231 | .try_into() 232 | .unwrap() 233 | ); 234 | } 235 | } 236 | }; 237 | 238 | 239 | quote! { 240 | #input 241 | 242 | impl #struct_name { 243 | pub fn read #long_generics (sensor: &mut #access_type #short_generics) -> Result> { 244 | let mut buff = [0; #buffer_size_str]; 245 | Self::read_more(sensor, &mut buff)?; 246 | 247 | // Process the buffer into the struct 248 | let mut val: [#data_type; #n_array] = [#init; #n_array]; 249 | 250 | #type_assignment 251 | 252 | 253 | Ok(Self(val)) 254 | } 255 | 256 | pub fn write #long_generics (&self, sensor: &mut #access_type #short_generics) -> Result<(), Error> { 257 | let mut buff = [0; #buffer_size_str]; 258 | for i in 0..#n_array { 259 | buff[i * #num_bytes..(i + 1) * #num_bytes].copy_from_slice(&self.0[i].#to_fn()); 260 | } 261 | 262 | #write 263 | } 264 | 265 | #[inline] 266 | pub fn read_more #long_generics (sensor: &mut #access_type #short_generics, buff: &mut [u8]) -> Result<(), Error> { 267 | #read 268 | } 269 | 270 | } 271 | } 272 | } 273 | 274 | } 275 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1 - Introduction 2 | 3 | This repository contains examples of *low-level* platform-independent drivers for [STMicroelectronics](https://www.st.com/mems) sensors. Sensor drivers and examples are written in Rust programming language. 4 | 5 | The STMicroelectronics naming convention for driver repositories is: 6 | - `PARTNUMBER-rs` (*e.g. lsm6dsv16x-rs*) for *low-level platform-independent drivers (PID)* 7 | 8 | ### 1.a - Repository structure 9 | 10 | This repository is structed with a folder for each sensor driver, named `xxxxxxx-rs`, where `xxxxxxx` is the sensor part number. 11 | 12 | Another folder, named `util`, does not follow the above naming convention. It contains *other useful resources* such as libraries and crates. To `clone` the complete content of this folder, use the following command: 13 | 14 | ```git 15 | git clone --recursive https://github.com/STMicroelectronics/st-mems-rust-drivers.git 16 | ``` 17 | 18 | ### 1.b - Sensor driver folder structure 19 | 20 | Every *sensor driver* folder contains the following: 21 | 22 | - `xxxxxxx-rs` : This folder is hosted as a submodule repository and published as a standalone crate on the [crates.io](https://crates.io/). Documentation can be found on the corresponding [crates.io](https://crates.io/) page or generated locally using the command: `cargo doc`. 23 | - `xxxxxxx-rs/examples`: This folder contains self-contained example projects to test the sensor. It may be necessary to modify the pin configuration or the I2C/SPI address as needed. The folder name of each examples includes the board used to test the sensor. 24 | - `xxxxxxx-rs/README`: Contains additional info about the specific driver. 25 | 26 | ### 1.c - Getting started 27 | 28 | A template is available in the `util` folder to help get started quickly with these drivers. The [cargo-generate](https://crates.io/crates/cargo-generate) tool may be used to configure a basic project environment by running: 29 | 30 | ```bash 31 | cargo generate --git https://github.com/STMicroelectronics/st-mems-rust-drivers util/st-template 32 | ``` 33 | 34 | This template allows customization of the starting project by selecting the desired Nucleo board, framework ([Embassy](https://crates.io/crates/embassy-stm32) or [stm32-rs](https://github.com/stm32-rs)), and sensor. It also includes examples showing how to use I2C communication. 35 | 36 | ------ 37 | 38 | # 2 - Integration details 39 | 40 | The driver is platform-independent. To use it on a general configuration, you need to: 41 | - Set up the sensor hardware bus (e.g., SPI or I2C). 42 | - Provide the configured bus instance to the sensor library. 43 | - When necessary, configure the interrupt pin and implement platform-specific delay functions. 44 | 45 | ### 2.a Source code integration 46 | 47 | Typically, the code can be used as presented in the example folder. However, to generalize the driver, a `BusOperation` trait is used. This allows for a generic bus that could be either I2C or SPI. The `util` folder wraps the trait in the [st-mems-bus](https://github.com/STMicroelectronics/st-mems-rust-drivers/tree/main/util/st-mems-bus) crate, enabling the same trait to be shared across all sensors and used simultaneously without redefining the trait. The configuration depends on the framework being used. Below is a minimal example with `sensorDriverCrate` referring to the specific driver crate and `SensorDriver` referring to the library's struct. Implementation for Embassy and STM32 frameworks are provided: 48 | 49 | - **Embassy I2C**: 50 | ```rust 51 | use embassy_stm32::{bind_interrupts, Config}; 52 | use embassy_stm32::dma::NoDma; 53 | use embassy_stm32::exti::ExtiInput; 54 | use embassy_stm32::gpio::{Input, Pull}; 55 | use embassy_stm32::i2c::{self, Config as I2cConfig, I2c}; 56 | use embassy_stm32::time::khz; 57 | use embassy_stm32::peripherals::{self, USART2}; 58 | 59 | bind_interrupts!(struct Irqs { 60 | USART2 => BufferedInterruptHandler; 61 | I2C1_EV => i2c::EventInterruptHandler; 62 | I2C1_ER => i2c::ErrorInterruptHandler; 63 | }); 64 | 65 | // main section 66 | 67 | let p = embassy_stm32::init(Default::default()); 68 | 69 | let i2c: I2c<_> = I2c::new( 70 | p.I2C1, // TBD: define the I2C channel as needed 71 | p.PB8, // TBD: define the scl route 72 | p.PB9, // TBD: define the sda route 73 | Irqs, 74 | NoDma, // TBD: provide Dma if available 75 | NoDma, // TBD: provide Dma if available 76 | khz(400), 77 | I2cConfig::default(), 78 | ); 79 | 80 | let interrupt_pin = p.PC0; // TBD: define the interrupt pin accordingly 81 | let exti = p.EXTI0; // TBD: define the EXTI related to the interrupt pin 82 | let interrupt = Input::new(interrupt_pin, Pull::None); 83 | let mut interrupt = ExtiInput::new(interrupt, exti); 84 | 85 | let i2c_addr = sensorDriverCrate::I2CAddress::I2cAddH; // TBD: depends on whether SDA0 is high or not; see sensor's datasheet. 86 | 87 | let mut sensor = sensorDriverCrate::SensorDriver::new_i2c(i2c, i2c_addr).unwrap(); 88 | ``` 89 | 90 | - **STM32 I2C**: 91 | ```rust 92 | use stm32f4xx_hal::{ 93 | i2c::{DutyCycle, I2c, Mode}, 94 | pac, 95 | prelude::*, 96 | serial::{config::Config, Serial}, 97 | }; 98 | 99 | // main section 100 | 101 | let dp = pac::Peripherals::take().unwrap(); 102 | let cp = cortex_m::Peripherals::take().unwrap(); 103 | 104 | let rcc = dp.RCC.constrain(); 105 | let clocks = rcc.cfgr.use_hse(8.MHz()).freeze(); 106 | 107 | let gpiob = dp.GPIOB.split(); 108 | let gpioa = dp.GPIOA.split(); 109 | 110 | let scl = gpiob.pb8; // TBD: define the scl pin 111 | let sda = gpiob.pb9; // TBD: define the sda pin 112 | 113 | let i2c = I2c::new( 114 | dp.I2C1, 115 | (scl, sda), 116 | Mode::Standard { 117 | frequency: 400.kHz(), 118 | }, 119 | &clocks, 120 | ); 121 | 122 | let i2c_addr = sensorDriverCrate::I2CAddress::I2cAddH; // TBD: depends on whether SDA0 is high or not; see sensor's datasheet. 123 | 124 | let mut sensor = sensorDriverCrate::SensorDriver::new_i2c(i2c, i2c_addr).unwrap(); 125 | ``` 126 | 127 | - **Embassy SPI** 128 | ```rust 129 | use core::cell::RefCell; 130 | use static_cell::StaticCell; 131 | use embassy_sync::blocking_mutex::NoopMutex; 132 | use embassy_time::{Delay, Duration, Timer, WithTimeout}; 133 | 134 | use embassy_stm32 as hal; 135 | use hal::gpio::{Level, Output, Speed}; 136 | use hal::sai; 137 | use hal::spi::Spi; 138 | use hal::{bind_interrupts, peripherals, usb}; 139 | 140 | // main section 141 | 142 | let p = embassy_stm32::init(Default::default()); 143 | 144 | let mut config = Config::default(); 145 | config.mode = MODE_3; // TBD: define MODE 146 | config.frequency = embassy_stm32::time::Hertz(100_000); // TBD: define frequency 147 | let spi = Spi::new_blocking(p.SPI1, p.PA1, p.PA7, p.PA6, config); // TBD: define pins 148 | let bus = NoopMutex::new(RefCell::new(spi)); 149 | let bus = SPI_BUS.init(bus); 150 | let cs = Output::new(p.PA4, Level::High, Speed::VeryHigh); // TBD: define Chip select (CS) settings 151 | 152 | let spi = SpiDevice::new(bus, cs); 153 | let mut sensor = sensorDriverCrate::SensorDriver::new_spi(spi) 154 | ``` 155 | 156 | - **STM32 SPI**: 157 | ```rust 158 | use stm32f4xx_hal::spi::{Mode, NoMiso}; 159 | use embedded_hal_bus::spi::ExclusiveDevice; 160 | use stm32f4xx_hal::{ 161 | gpio::{self, Edge, Input}, 162 | i2c::{I2c}, 163 | spi::{Spi, Polarity, Phase}, 164 | pac::{self, interrupt}, 165 | prelude::*, 166 | serial::{config::Config, Serial}, 167 | }; 168 | 169 | // main section 170 | 171 | let dp = pac::Peripherals::take().unwrap(); 172 | let cp = cortex_m::Peripherals::take().unwrap(); 173 | 174 | let rcc = dp.RCC.constrain(); 175 | let clocks = rcc.cfgr.use_hse(8.MHz()).freeze(); 176 | 177 | let gpiob = dp.GPIOB.split(); 178 | let gpioa = dp.GPIOA.split(); 179 | 180 | // SPI pins: SCK, MISO, MOSI 181 | let sck = gpioa.pa5.into_alternate(); // TBD:: define the sck pin 182 | let miso = gpioa.pa6.into_alternate(); // TBD:: define the miso pin 183 | let mosi = gpioa.pa7.into_alternate(); // TBD:: define the mosi pin 184 | 185 | let scl = gpiob.pb8; // TBD: define the scl pin 186 | let sda = gpiob.pb9; // TBD: define the sda pin 187 | 188 | // Chip Select (CS) pin 189 | let mut cs = gpiob.pb6.into_push_pull_output(); // TBD: define the gpio pin 190 | cs.set_high(); // Deselect by default 191 | 192 | let spi = Spi::new( 193 | dp.SPI1, // TBD: define which SPIx to use 194 | (sck, miso, mosi), 195 | Mode { 196 | polarity: Polarity::IdleLow, 197 | phase: Phase::CaptureOnFirstTransition, 198 | }, 199 | 2.MHz(), 200 | &clocks, 201 | ); 202 | 203 | // Acquire SPI channel as Exclusive 204 | let spi_dev = ExclusiveDevice::new_no_delay(spi, cs).unwrap(); 205 | 206 | let mut sensor = sensorDriverCrate::SensorDriver::new_spi(spi_dev, tim1); 207 | ``` 208 | 209 | ### 2.b Required properties 210 | 211 | > * A rust compiler with a toolchain targeting the MCU. 212 | > * Each sensor specifies a Minimum Supported Rust Version (MSRV) to ensure compatibility and successful compilation. 213 | 214 | ------ 215 | 216 | # 3 - Running examples 217 | 218 | Examples are written for [STM32 Microcontrollers](https://www.st.com/en/microcontrollers.html) using the [NUCLEO_F401RE](https://github.com/STMicroelectronics/STMems_Standard_C_drivers/tree/master/_prj_NucleoF401) as primary platform. However, they can also serve as a guideline for every other platforms. 219 | 220 | ### 3.a Using STMicroelectronics evaluation boards 221 | 222 | When using supported STMicroelectronics evaluation boards, the schematics provide information about which pins to use to setup the I2C or SPI communication with the sensor. 223 | 224 | ------ 225 | 226 | **More information: [http://www.st.com](http://st.com/MEMS)** 227 | 228 | **Copyright (C) 2025 STMicroelectronics** -------------------------------------------------------------------------------- /util/st-mems-reg-config-conv/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::println; 3 | use std::path::Path; 4 | use std::{vec, format}; 5 | use std::string::ToString; 6 | use std::vec::Vec; 7 | use std::string::String; 8 | use serde::Deserialize; 9 | use crate::ucf_entry::{MemsUcfOp, UcfLineExt}; 10 | use std::ffi::CStr; 11 | use std::os::raw::c_char; 12 | 13 | #[derive(Deserialize)] 14 | #[allow(dead_code)] 15 | struct JsonFormat { 16 | #[serde(rename = "type")] 17 | json_type: String, 18 | version: String, 19 | } 20 | 21 | #[derive(Deserialize)] 22 | #[allow(dead_code)] 23 | struct Application { 24 | name: String, 25 | version: String, 26 | } 27 | 28 | #[derive(Deserialize, Debug)] 29 | #[serde(untagged)] 30 | #[allow(dead_code)] 31 | enum ConfigurationEntry { 32 | Comment { comment: String }, 33 | Operation { 34 | #[serde(rename = "type")] 35 | config_type: ConfigType, 36 | address: Option, 37 | data: String 38 | }, 39 | } 40 | 41 | #[derive(Deserialize, Debug, PartialEq)] 42 | #[allow(dead_code)] 43 | enum ConfigType { 44 | #[serde(rename = "write")] 45 | Write, 46 | #[serde(rename = "delay")] 47 | Delay, 48 | } 49 | 50 | #[derive(Deserialize)] 51 | #[allow(dead_code)] 52 | struct Sensor { 53 | name: Vec, 54 | configuration: Vec, 55 | outputs: Option> 56 | } 57 | 58 | #[derive(Deserialize)] 59 | #[allow(dead_code)] 60 | struct Output { 61 | name: String, 62 | core: String, 63 | #[serde(rename = "type")] 64 | output_type: String, 65 | len: String, 66 | reg_addr: String, 67 | reg_name: String, 68 | } 69 | 70 | #[derive(Deserialize)] 71 | #[allow(dead_code)] 72 | struct JsonData { 73 | json_format: JsonFormat, 74 | application: Application, 75 | description: Option, 76 | sensors: Vec, 77 | } 78 | 79 | #[repr(C)] 80 | pub enum FileType { 81 | Json, 82 | Ucf 83 | } 84 | 85 | /// Handles the .rs file generation from UCF/JSON configurations files for MLC/FSM/ISPU. 86 | /// 87 | /// Rust code could use the more specific alternatives: generate_rs_from_json, 88 | /// generate_rs_from_ucf; the purpose of this function is to expose an interface for C programs 89 | /// 90 | /// # Arguments 91 | /// 92 | /// * `input_file`: c_char string pointing to the path of the input file (.ucf/.json) 93 | /// * `output_file`: c_char string pointing to the path where to save the .rs file generated 94 | /// * `array_name`: c_char string used to set the array's name containing the configurations 95 | /// * `sensor_id`: c_char string used to ensure load of right configuration (applied only for json 96 | /// configurations) 97 | /// * `file_type`: FileType enum is used to select how to handle input_file (allowed values are 98 | /// Json/Ucf). 99 | /// 100 | /// # Safety 101 | /// - Each c_char string should contain a valid nul terminator at the end of the string 102 | /// - Each c_char is a valid pointer: 103 | /// - entire memory range is contained in a single allocation 104 | /// - should be non-null even for a zero-length content 105 | /// - Memory pointed by c_char pointers should not change during the execution of the function 106 | /// - The nul terminator for c_char pointers mut be before isize::MAX 107 | #[unsafe(no_mangle)] 108 | pub unsafe extern "C" fn generate_rs( 109 | input_file: *const c_char, 110 | output_file: *const c_char, 111 | array_name: *const c_char, 112 | sensor_id: *const c_char, 113 | file_type: FileType, 114 | ) -> i32 { 115 | 116 | // Check for null pointers 117 | if input_file.is_null() || output_file.is_null() || array_name.is_null() || sensor_id.is_null() { 118 | return -1; // error code 119 | } 120 | 121 | let input_file = unsafe { CStr::from_ptr(input_file) }.to_str().unwrap(); 122 | let output_file = unsafe { CStr::from_ptr(output_file) }.to_str().unwrap(); 123 | let array_name = unsafe { CStr::from_ptr(array_name) }.to_str().unwrap(); 124 | let sensor_id = unsafe { CStr::from_ptr(sensor_id) }.to_str().unwrap(); 125 | 126 | let input_path = Path::new(input_file); 127 | let output_path = Path::new(output_file); 128 | 129 | match file_type { 130 | FileType::Json => generate_rs_from_json(input_path, output_path, array_name, sensor_id, false), 131 | FileType::Ucf => generate_rs_from_ucf(input_path, output_path, array_name), 132 | }; 133 | 134 | 0 135 | } 136 | 137 | pub fn generate_rs_from_json(input_file: &Path, output_file: &Path, array_name: &str, sensor_id: &str, verbose: bool) { 138 | let content = fs::read_to_string(input_file).expect("Failed to read input file"); 139 | let json_data: JsonData = serde_json::from_str(&content).expect("Failed to parse JSON"); 140 | 141 | // Parse configuration lines 142 | let mut config_lines = vec![]; 143 | let mut found_one_config = false; 144 | for sensor in json_data.sensors { 145 | for name in &sensor.name { 146 | println!("{name}"); 147 | } 148 | if !sensor.name.contains(&sensor_id.to_uppercase()) { 149 | continue; 150 | } 151 | for config in sensor.configuration { 152 | match config { 153 | ConfigurationEntry::Comment { .. } => { 154 | 155 | }, 156 | ConfigurationEntry::Operation { 157 | config_type, address, data 158 | } => { 159 | if config_type == ConfigType::Write { 160 | let address = address.unwrap(); 161 | let address = u8::from_str_radix(&address[2..], 16).expect("Invalid address format"); 162 | let data = u8::from_str_radix(&data[2..], 16).expect("Invalid data format"); 163 | config_lines.push(UcfLineExt { 164 | op: MemsUcfOp::Write, 165 | address, 166 | data, 167 | }); 168 | } else if config_type == ConfigType::Delay { 169 | let data = data.parse::().expect("Cannot parse milliseconds of delay"); 170 | config_lines.push(UcfLineExt { 171 | op: MemsUcfOp::Delay, 172 | address: 0x0, 173 | data 174 | }); 175 | } 176 | } 177 | } 178 | } 179 | 180 | if verbose { 181 | println!("-- Summary of outputs generated by json configuration, open json for more details --"); 182 | 183 | if let Some(outputs) = sensor.outputs { 184 | for output in outputs { 185 | println!("{}, {}", output.name, output.reg_name); 186 | } 187 | } 188 | } 189 | 190 | found_one_config = true; 191 | break; 192 | } 193 | 194 | if !found_one_config { 195 | panic!("No configuration found for the sensor id"); 196 | } 197 | 198 | // Generate Rust code 199 | let mut output = String::new(); 200 | output.push_str(r#"// DO NOT EDIT. File autogenerated during each build from "#); 201 | output.push_str(&format!("JSON file: {}\n", input_file.display()).to_string()); 202 | output.push_str(r#"// Change build.rs script to change JSON source file"#); 203 | output.push_str("\nuse st_mems_reg_config_conv::ucf_entry::*;\n\n"); 204 | output.push_str(&format!("#[rustfmt::skip]\npub const {array_name}: [UcfLineExt; ").to_string()); 205 | output.push_str(&config_lines.len().to_string()); 206 | output.push_str("] = [\n"); 207 | 208 | for config_line in &config_lines { 209 | let line = match config_line.op { 210 | MemsUcfOp::Delay => &format!( 211 | " UcfLineExt {{ op: MemsUcfOp::{}, address: 0x{:02X}, data: {} }},\n", 212 | config_line.op.to_string(), config_line.address, config_line.data 213 | ), 214 | _ => &format!( 215 | " UcfLineExt {{ op: MemsUcfOp::{}, address: 0x{:02X}, data: 0x{:02X} }},\n", 216 | config_line.op.to_string(), config_line.address, config_line.data 217 | ) 218 | }; 219 | output.push_str(line); 220 | } 221 | 222 | output.push_str("];\n"); 223 | 224 | // Write to output file 225 | fs::write(output_file, output).expect("Failed to write output file"); 226 | } 227 | 228 | pub fn generate_rs_from_ucf(input_file: &Path, output_file: &Path, array_name: &str) { 229 | let content = fs::read_to_string(input_file).expect("Failed to read input file"); 230 | let mut lines = content.lines(); 231 | 232 | // Skip comment lines 233 | for line in lines.by_ref() { 234 | if !line.starts_with("--") { 235 | break; 236 | } 237 | } 238 | 239 | // Parse remaining lines 240 | let mut ucf_lines = vec![]; 241 | for line in lines { 242 | if let Some(ucf_entry) = parse_line(line) { 243 | ucf_lines.push(ucf_entry); 244 | } 245 | } 246 | 247 | // Generate Rust code 248 | let mut output = String::new(); 249 | output.push_str(r#"// DO NOT EDIT. File autogenerated during each build from "#); 250 | output.push_str(&format!("ucf file: {} \n", input_file.display()).to_string()); 251 | output.push_str(r#"// Change build.rs script to change ucf source file"#); 252 | output.push_str("\nuse st_mems_reg_config_conv::ucf_entry::*;\n\n"); 253 | output.push_str(&format!("#[rustfmt::skip]\npub const {array_name}: [UcfLineExt; ").to_string()); 254 | output.push_str(&ucf_lines.len().to_string()); 255 | output.push_str("] = [\n"); 256 | 257 | for ucf_line in &ucf_lines { 258 | let line = match ucf_line.op { 259 | MemsUcfOp::Delay => &format!( 260 | " UcfLineExt {{ op: MemsUcfOp::{}, address: 0x{:02X}, data: {} }},\n", 261 | ucf_line.op.to_string(), ucf_line.address, ucf_line.data 262 | ), 263 | _ => &format!( 264 | " UcfLineExt {{ op: MemsUcfOp::{}, address: 0x{:02X}, data: 0x{:02X} }},\n", 265 | ucf_line.op.to_string(), ucf_line.address, ucf_line.data 266 | ) 267 | }; 268 | output.push_str(line); 269 | } 270 | 271 | output.push_str("];\n"); 272 | 273 | // Write to output file 274 | fs::write(output_file, output).expect("Failed to write output file"); 275 | } 276 | 277 | fn parse_line(line: &str) -> Option { 278 | let parts: Vec<&str> = line.split_whitespace().collect(); 279 | if parts.len() == 2 && parts[0] == "WAIT" { 280 | return Some(UcfLineExt { 281 | op: MemsUcfOp::Delay, 282 | address: 0, 283 | data: parts[1].parse::().expect("Cannot parse milliseconds of delay") 284 | }) 285 | } 286 | if parts.len() == 3 && parts[0] == "Ac" { 287 | let address = u8::from_str_radix(parts[1], 16).ok()?; 288 | let data = u8::from_str_radix(parts[2], 16).ok()?; 289 | return Some(UcfLineExt { 290 | op: MemsUcfOp::Write, 291 | address, 292 | data 293 | }) 294 | } 295 | 296 | None 297 | } 298 | -------------------------------------------------------------------------------- /util/st-template/pre-script.rhai: -------------------------------------------------------------------------------- 1 | let chip = variable::get("mcu").to_lower(); 2 | let sensor = variable::get("sensor"); 3 | let sensor_capitalized = capitalize(sensor); 4 | 5 | // split sensor parts (example based on: stm32l151r8t6) 6 | let name = chip; 7 | let family = name[0..7]; // "stm32l1" 8 | let series = name[6..9]; // "151" 9 | let flash = name[10]; // "8" 10 | 11 | print(series); 12 | 13 | let svd_name = name[0..9]; 14 | 15 | // optional values 16 | // let package_name = name[11]; // "t" 17 | // let temp = name[12]; // "6" 18 | 19 | // compute flash and ram size 20 | let flash_size_kb = switch flash { 21 | '4' => 16, 22 | '6' => 32, 23 | '8' => 64, 24 | 'b' => 128, 25 | 'c' => 256, 26 | 'd' => 384, 27 | 'e' => 512, 28 | 'f' => 1024, 29 | _ => throw "Unknown flash size" 30 | }; 31 | 32 | let ram_kb = get_ram_kb(family, series, flash_size_kb); 33 | 34 | // Get TCM/CCM memory configurations 35 | let tcm_config = get_tcm_config(family, series); 36 | let itcm_kb = tcm_config.itcm; 37 | let dtcm_kb = tcm_config.dtcm; 38 | let ccm_kb = tcm_config.ccm; 39 | 40 | // Determine target triple based on MCU family 41 | let target = get_target(family); 42 | 43 | // Determine OpenOCD target configuration 44 | let openocd_target = get_openocd_target(family); 45 | 46 | fn capitalize(s) { 47 | if s.len > 0 { 48 | s[0].to_upper() + s[1..s.len] 49 | } else { 50 | s 51 | } 52 | } 53 | 54 | /// Returns target triple for different STM32 families 55 | fn get_target(family) { 56 | switch family { 57 | // Cortex-M0/M0+ 58 | "stm32f0" | "stm32g0" | "stm32c0" | "stm32l0" => "thumbv6m-none-eabi", 59 | 60 | // Cortex-M3 61 | "stm32f1" | "stm32l1" => "thumbv7m-none-eabi", 62 | 63 | // Cortex-M4F (with FPU) 64 | "stm32f3" | "stm32f4" | "stm32g4" | "stm32l4" | "stm32wl" => "thumbv7em-none-eabihf", 65 | 66 | // Cortex-M7F (with FPU and double precision) 67 | "stm32f7" | "stm32h7" => "thumbv7em-none-eabihf", 68 | 69 | _ => "thumbv7em-none-eabihf" // default 70 | } 71 | } 72 | 73 | /// Returns OpenOCD target configuration file name (without .cfg extension) 74 | fn get_openocd_target(family) { 75 | switch family { 76 | "stm32f0" => "stm32f0x", 77 | "stm32f1" => "stm32f1x", 78 | "stm32f2" => "stm32f2x", 79 | "stm32f3" => "stm32f3x", 80 | "stm32f4" => "stm32f4x", 81 | "stm32f7" => "stm32f7x", 82 | "stm32g0" => "stm32g0x", 83 | "stm32g4" => "stm32g4x", 84 | "stm32h7" => "stm32h7x", 85 | "stm32l0" => "stm32l0", 86 | "stm32l1" => "stm32l1", 87 | "stm32l4" => "stm32l4x", 88 | "stm32l5" => "stm32l5x", 89 | "stm32c0" => "stm32c0x", 90 | "stm32wl" => "stm32wlx", 91 | "stm32wb" => "stm32wbx", 92 | _ => "stm32f4x" // default fallback 93 | } 94 | } 95 | 96 | /// Returns TCM and CCM configuration for STM32 families 97 | /// Returns a map with itcm, dtcm, and ccm sizes in KB (0 if not available) 98 | fn get_tcm_config(family, series) { 99 | let config = #{ 100 | itcm: 0, 101 | dtcm: 0, 102 | ccm: 0 103 | }; 104 | 105 | switch family { 106 | // STM32F3 has CCM (Core Coupled Memory) 107 | "stm32f3" => { 108 | config.ccm = switch series { 109 | "301" | "318" => 0, 110 | "302" | "303" => 8, 111 | "328" | "334" => 4, 112 | "358" => 8, 113 | "373" | "378" => 0, 114 | "398" => 8, 115 | _ => 0 116 | }; 117 | }, 118 | 119 | // STM32F4 has CCM 120 | "stm32f4" => { 121 | config.ccm = switch series { 122 | "401" | "410" | "411" | "412" => 0, 123 | "405" | "407" | "415" | "417" => 64, 124 | "413" | "423" => 64, 125 | "427" | "429" | "437" | "439" => 64, 126 | "446" => 0, 127 | "469" | "479" => 64, 128 | _ => 0 129 | }; 130 | }, 131 | 132 | // STM32F7 has ITCM and DTCM 133 | "stm32f7" => { 134 | // F7 series has 16KB ITCM and 64-128KB DTCM 135 | config.itcm = 16; 136 | config.dtcm = switch series { 137 | "722" | "723" | "730" | "732" | "733" => 64, 138 | "745" | "746" | "750" | "756" => 64, 139 | "765" | "767" | "768" | "769" | "777" | "778" | "779" => 128, 140 | _ => 64 141 | }; 142 | }, 143 | 144 | // STM32H7 has ITCM and DTCM (not in your original script, but adding for completeness) 145 | "stm32h7" => { 146 | config.itcm = 64; 147 | config.dtcm = 128; 148 | }, 149 | 150 | _ => { 151 | // No TCM/CCM for other families 152 | } 153 | }; 154 | 155 | config 156 | } 157 | 158 | /// Returns RAM size in KB for a given STM32 family, series, and flash size. 159 | /// Throws an error if the combination is unknown. 160 | fn get_ram_kb(family, series, flash_size_kb) { 161 | switch family { 162 | // STM32F0 family 163 | "stm32f0" => switch series { 164 | "030" | "031" | "038" => switch flash_size_kb { 165 | 16 | 32 => 4, 166 | 64 => 8, 167 | 256 => 32, 168 | _ => throw "Unknown RAM size for STM32F0[030/031/038]" 169 | }, 170 | "042" | "048" => 6, 171 | "051" | "058" => 8, 172 | "070" | "071" | "072" | "078" => switch flash_size_kb { 173 | 32 => 6, 174 | 64 | 128 => 16, 175 | _ => throw "Unknown RAM size for STM32F0[070/071/072/078]" 176 | }, 177 | "091" | "098" => 32, 178 | _ => throw "Unknown RAM size for STM32F0" 179 | }, 180 | 181 | // STM32F1 family 182 | "stm32f1" => switch series { 183 | "100" => switch flash_size_kb { 184 | 16 | 32 => 4, 185 | 64 | 128 => 8, 186 | 256 => 24, 187 | 384 | 512 => 32, 188 | _ => throw "Unknown RAM size for STM32F100" 189 | }, 190 | "101" | "102" => switch flash_size_kb { 191 | 16 => 4, 192 | 32 => 6, 193 | 64 => 10, 194 | 128 => 16, 195 | 256 => 32, 196 | 384 | 512 => 48, 197 | 768 | 1024 => 80, 198 | _ => throw "Unknown RAM size for STM32F101/102" 199 | }, 200 | "103" => switch flash_size_kb { 201 | 16 => 6, 202 | 32 => 10, 203 | 64 | 128 => 20, 204 | 256 => 48, 205 | 384 | 512 => 64, 206 | 768 | 1024 => 96, 207 | _ => throw "Unknown RAM size for STM32F103" 208 | }, 209 | "105" | "107" => 64, 210 | _ => throw "Unknown RAM size for STM32F1" 211 | }, 212 | 213 | // STM32F3 family 214 | "stm32f3" => switch series { 215 | "301" | "318" => 16, 216 | "302" => switch flash_size_kb { 217 | 32 | 64 => 16, 218 | 128 => 32, 219 | 256 => 40, 220 | 384 | 512 => 64, 221 | _ => throw "Unknown RAM size for STM32F302" 222 | }, 223 | "303" => switch flash_size_kb { 224 | 32 | 64 => 12, 225 | 128 => 32, 226 | 256 => 40, 227 | 384 | 512 => 64, 228 | _ => throw "Unknown RAM size for STM32F303" 229 | }, 230 | "328" | "334" => 12, 231 | "358" => 40, 232 | "373" => switch flash_size_kb { 233 | 64 => 16, 234 | 128 => 24, 235 | 256 => 32, 236 | _ => throw "Unknown RAM size for STM32F373" 237 | }, 238 | "378" => 32, 239 | "398" => 64, 240 | _ => throw "Unknown RAM size for STM32F3" 241 | }, 242 | 243 | // STM32F4 family 244 | "stm32f4" => switch series { 245 | "401" => switch flash_size_kb { 246 | 128 | 256 => 64, 247 | 384 | 512 => 96, 248 | _ => throw "Unknown RAM size for STM32F401" 249 | }, 250 | "405" | "407" | "415" | "417" => 128, // 112+16 251 | "410" => 32, 252 | "411" => 128, 253 | "412" => 256, 254 | "413" | "423" => 320, // 256+64 255 | "427" | "429" | "437" | "439" => 192, // 112+16+64 256 | "446" => 128, // 112+16 257 | "469" | "479" => 320, // 160+32+128 258 | _ => throw "Unknown RAM size for STM32F4" 259 | }, 260 | 261 | // STM32F7 family 262 | "stm32f7" => switch series { 263 | "722" | "723" | "730" | "732" | "733" => 192, // 176+16 264 | "745" | "746" | "750" | "756" => 256, // 240+16 265 | "765" | "767" | "768" | "769" | "777" | "778" | "779" => 384, // 368+16 266 | _ => throw "Unknown RAM size for STM32F7" 267 | }, 268 | 269 | // STM32G0 family 270 | "stm32g0" => switch series { 271 | "030" | "031" | "041" => 8, 272 | "050" | "051" | "061" => 16, 273 | "070" | "071" | "081" => 32, 274 | "0b0" | "0b1" | "0c1" => 128, 275 | _ => throw "Unknown RAM size for STM32G0" 276 | }, 277 | 278 | // STM32G4 family 279 | "stm32g4" => switch series { 280 | "431" | "441" => 32, // 16+6+10 281 | "473" | "474" | "483" | "484" => 128, // 80+16+32 282 | "491" | "4a1" => 112, // 80+16+16 283 | _ => throw "Unknown RAM size for STM32G4" 284 | }, 285 | 286 | // STM32C0 family 287 | "stm32c0" => switch series { 288 | "011" => 6, 289 | "031" => 12, 290 | _ => throw "Unknown RAM size for STM32C0" 291 | }, 292 | 293 | // STM32L0 family 294 | "stm32l0" => switch series { 295 | "010" | "011" | "021" | "031" | "041" | "051" | "052" | "053" | "062" | "063" => switch flash_size_kb { 296 | 8 | 16 => 2, 297 | 32 | 64 => 8, 298 | 128 => 20, 299 | _ => throw "Unknown RAM size for STM32L0" 300 | }, 301 | "071" | "072" | "073" | "081" | "082" | "083" => 20, 302 | _ => throw "Unknown RAM size for STM32L0" 303 | }, 304 | 305 | // STM32L1 family 306 | "stm32l1" => switch series { 307 | "100" => switch flash_size_kb { 308 | 32 => 4, 309 | 64 => 8, 310 | 128 => 10, 311 | 256 => 16, 312 | _ => throw "Unknown RAM size for STM32L100" 313 | }, 314 | "151" | "152" => switch flash_size_kb { 315 | 32 | 64 => 10, 316 | 128 => 16, 317 | 256 => 32, 318 | 384 => 48, 319 | 512 => 80, 320 | _ => throw "Unknown RAM size for STM32L151/152" 321 | }, 322 | "162" => switch flash_size_kb { 323 | 256 => 32, 324 | 384 => 48, 325 | 512 => 80, 326 | _ => throw "Unknown RAM size for STM32L162" 327 | }, 328 | _ => throw "Unknown RAM size for STM32L1" 329 | }, 330 | 331 | // STM32L4 family 332 | "stm32l4" => switch series { 333 | "412" | "422" => 40, // 32+8 334 | "431" | "432" | "433" | "442" | "443" => 64, // 48+16 335 | "451" | "452" | "462" => 160, 336 | "471" | "475" | "476" | "486" => 128, 337 | "496" | "4a6" => 320, // 256+64 338 | _ => throw "Unknown RAM size for STM32L4" 339 | }, 340 | 341 | // STM32WL family 342 | "stm32wl" => switch series { 343 | "54" | "55" => 64, // 32+32 344 | "e4" | "e5" => switch flash_size_kb { 345 | 64 => 20, 346 | 128 => 48, 347 | 256 => 64, 348 | _ => throw "Unknown RAM size for STM32WL" 349 | }, 350 | _ => throw "Unknown RAM size for STM32WL" 351 | }, 352 | 353 | _ => throw "Unknown STM32 family" 354 | } 355 | } 356 | 357 | 358 | /// Configuration of the sensor who_am_i example 359 | let address = "0x00"; 360 | let delay = ", delay"; 361 | let who_am_i_fn = "device_id_get()"; 362 | let id = "ID"; 363 | 364 | switch sensor { 365 | "iis2dlpc" => { 366 | address = "I2CAddress::I2cAddH"; 367 | }, 368 | "lps22hh" => { 369 | address = "I2CAddress::AddressH"; 370 | delay = ""; 371 | }, 372 | "lsm6dso16is" | "ism330is" => { 373 | address = "I2CAddress::I2cAddL"; 374 | }, 375 | "lsm6dsv16x" => { 376 | address = "I2CAddress::I2cAddH"; 377 | }, 378 | "lsm6dsv320x" | "lsm6dsv80x" | "ism6hg256x" => { 379 | address = "I2CAddress::I2cAddL"; 380 | }, 381 | "iis2mdc" => { 382 | address = "I2CAddress::I2cAdd"; 383 | id = "IIS2MDC"; 384 | }, 385 | "ilps22qs" => { 386 | address = "I2CAddress::I2cAdd"; 387 | who_am_i_fn = "id_get()"; 388 | id = "ILPS22QS_ID"; 389 | }, 390 | "ism330dhcx" => { 391 | address = "I2CAddress::I2cAddH"; 392 | id = "ISM330DHCX_ID"; 393 | }, 394 | "lis2dux12" | "lis2duxs12" | "iis2dulpx" => { 395 | address = "I2CAddress::I2cAddH"; 396 | }, 397 | "lis2mdl" => { 398 | address = "I2CAddress::I2cAdd"; 399 | delay = ""; 400 | }, 401 | "lps22df" => { 402 | address = "I2CAddress::I2cAddH"; 403 | } 404 | } 405 | 406 | // Set all variables 407 | variable::set("id", id); 408 | variable::set("delay", delay); 409 | variable::set("who_am_i_fn", who_am_i_fn); 410 | variable::set("address", address); 411 | variable::set("sensor_capitalized", sensor_capitalized); 412 | variable::set("ram_kb", ram_kb.to_string()); 413 | variable::set("flash_size_kb", flash_size_kb.to_string()); 414 | variable::set("target", target); 415 | variable::set("openocd_target", openocd_target); 416 | variable::set("itcm_kb", itcm_kb.to_string()); 417 | variable::set("dtcm_kb", dtcm_kb.to_string()); 418 | variable::set("ccm_kb", ccm_kb.to_string()); 419 | variable::set("svd_name", svd_name); 420 | -------------------------------------------------------------------------------- /util/st-fifo-tool/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | mod sensor_data; 4 | 5 | use core::fmt; 6 | use sensor_data::*; 7 | 8 | pub static DEVICES: [Device; 2] = [ 9 | Device { 10 | bdr_acc: [0.0, 13.0, 26.0, 52.0, 104.0, 208.0, 416.0, 833.0, 1666.0, 3333.0, 6666.0, 1.625, 0.0, 0.0, 0.0, 0.0], 11 | bdr_gyr: [0.0, 13.0, 26.0, 52.0, 104.0, 208.0, 416.0, 833.0, 1666.0, 3333.0, 6666.0, 0.0, 0.0, 0.0, 0.0, 0.0], 12 | bdr_vsens: [0.0, 13.0, 26.0, 52.0, 104.0, 208.0, 416.0, 0.0, 0.0, 0.0, 0.0, 1.625, 0.0, 0.0, 0.0, 0.0], 13 | dtime: [0, 3072, 1536, 768, 384, 192, 96, 48, 24, 12, 6, 24576, 0, 0, 0, 0], 14 | tag_valid_limit: 0x19, 15 | }, 16 | Device { 17 | bdr_acc: [0.0, 1.875, 7.5, 15.0, 30.0, 60.0, 120.0, 240.0, 480.0, 960.0, 1920.0, 3840.0, 7680.0, 0.0, 0.0, 0.0], 18 | bdr_gyr: [0.0, 1.875, 7.5, 15.0, 30.0, 60.0, 120.0, 240.0, 480.0, 960.0, 1920.0, 3840.0, 7680.0, 0.0, 0.0, 0.0], 19 | bdr_vsens: [0.0, 1.875, 7.5, 15.0, 30.0, 60.0, 120.0, 240.0, 480.0, 960.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 20 | dtime: [0, 24576, 6144, 3072, 1536, 768, 384, 192, 96, 48, 24, 12, 6, 0, 0, 0], 21 | tag_valid_limit: 0x1E, 22 | }, 23 | ]; 24 | 25 | pub fn max(a: F, b: F) -> F { 26 | if a > b { a } else { b } 27 | } 28 | 29 | pub fn min(a: i32, b: i32) -> i32 { 30 | if a < b { a } else { b } 31 | } 32 | 33 | #[derive(Clone)] 34 | pub struct FifoData { 35 | fifo_ver: u8, 36 | tag_counter_old: u8, 37 | dtime_xl: u32, 38 | dtime_gy: u32, 39 | dtime_min: u32, 40 | dtime_xl_old: u32, 41 | dtime_gy_old: u32, 42 | timestamp: u32, 43 | last_timestamp_xl: u32, 44 | last_timestamp_gy: u32, 45 | bdr_chg_xl_flag: u8, 46 | bdr_chg_gy_flag: u8, 47 | last_data_xl: [i16; 3], 48 | last_data_gy: [i16; 3], 49 | } 50 | 51 | impl FifoData { 52 | 53 | pub fn init(conf: &Config) -> Result { 54 | let bdr_xl = conf.bdr_xl; 55 | let bdr_gy = conf.bdr_gy; 56 | let bdr_vsens = conf.bdr_vsens; 57 | let bdr_max = max(bdr_xl, bdr_gy); 58 | let bdr_max = max(bdr_max, bdr_vsens); 59 | 60 | if bdr_xl < 0.0 || bdr_gy < 0.0 || bdr_vsens < 0.0 { 61 | return Err(Status::Err); 62 | } 63 | 64 | let fifo_ver = if conf.device < DeviceType::Lsm6dsv { 0 } else { 1 }; 65 | 66 | let sensor_data = FifoData { 67 | fifo_ver, 68 | tag_counter_old: 0, 69 | dtime_xl: DEVICES[fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[fifo_ver as usize].bdr_acc, bdr_xl)], 70 | dtime_gy: DEVICES[fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[fifo_ver as usize].bdr_gyr, bdr_gy)], 71 | dtime_min: DEVICES[fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[fifo_ver as usize].bdr_acc, bdr_max)], 72 | dtime_xl_old: DEVICES[fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[fifo_ver as usize].bdr_acc, bdr_xl)], 73 | dtime_gy_old: DEVICES[fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[fifo_ver as usize].bdr_gyr, bdr_gy)], 74 | timestamp: 0, 75 | last_timestamp_xl: 0, 76 | last_timestamp_gy: 0, 77 | bdr_chg_xl_flag: 0, 78 | bdr_chg_gy_flag: 0, 79 | last_data_xl: [0; 3], 80 | last_data_gy: [0; 3], 81 | }; 82 | 83 | Ok(sensor_data) 84 | } 85 | 86 | pub fn decode( 87 | &mut self, 88 | fifo_out_slot: &mut [OutSlot], 89 | fifo_raw_slot: &[RawSlot], 90 | out_slot_size: &mut u16, 91 | stream_size: u16, 92 | ) -> Status { 93 | let mut j = 0; 94 | 95 | for i in 0..stream_size as usize { 96 | let tag = (fifo_raw_slot[i].fifo_data_out[0] & TagMask::Sensor as u8) >> TagShift::Sensor as u8; 97 | let tag_counter = (fifo_raw_slot[i].fifo_data_out[0] & TagMask::Counter as u8) >> TagShift::Counter as u8; 98 | 99 | if self.fifo_ver == 0 && FifoData::has_even_parity(fifo_raw_slot[i].fifo_data_out[0]) { 100 | return Status::Err; 101 | } 102 | 103 | if !self.is_tag_valid(tag) { 104 | return Status::Err; 105 | } 106 | 107 | if tag_counter != self.tag_counter_old && self.dtime_min != 0 { 108 | let diff_tag_counter = if tag_counter < self.tag_counter_old { 109 | tag_counter + 4 - self.tag_counter_old 110 | } else { 111 | tag_counter - self.tag_counter_old 112 | }; 113 | 114 | self.timestamp += self.dtime_min * diff_tag_counter as u32; 115 | } 116 | 117 | let tag = Tag::try_from(tag).map_err(|_| Status::Err).unwrap(); 118 | 119 | if tag == Tag::Odrchg { 120 | let bdr_acc_cfg = (fifo_raw_slot[i].fifo_data_out[6] & BdrMask::Xl as u8) >> BdrShift::Xl as u8; 121 | let bdr_gyr_cfg = (fifo_raw_slot[i].fifo_data_out[6] & BdrMask::Gy as u8) >> BdrShift::Gy as u8; 122 | let bdr_vsens_cfg = (fifo_raw_slot[i].fifo_data_out[4] & BdrMask::Vsens as u8) >> BdrShift::Vsens as u8; 123 | 124 | let bdr_xl = DEVICES[self.fifo_ver as usize].bdr_acc[bdr_acc_cfg as usize]; 125 | let bdr_gy = DEVICES[self.fifo_ver as usize].bdr_gyr[bdr_gyr_cfg as usize]; 126 | let bdr_vsens = DEVICES[self.fifo_ver as usize].bdr_vsens[bdr_vsens_cfg as usize]; 127 | let bdr_max = max(max(bdr_xl, bdr_gy), bdr_vsens); 128 | 129 | self.dtime_xl_old = self.dtime_xl; 130 | self.dtime_gy_old = self.dtime_gy; 131 | self.dtime_min = DEVICES[self.fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[self.fifo_ver as usize].bdr_acc, bdr_max)]; 132 | self.dtime_xl = DEVICES[self.fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[self.fifo_ver as usize].bdr_acc, bdr_xl)]; 133 | self.dtime_gy = DEVICES[self.fifo_ver as usize].dtime[FifoData::bdr_get_index(&DEVICES[self.fifo_ver as usize].bdr_gyr, bdr_gy)]; 134 | 135 | self.bdr_chg_xl_flag = 1; 136 | self.bdr_chg_gy_flag = 1; 137 | } else if tag == Tag::Ts { 138 | self.timestamp = u32::from_le_bytes(fifo_raw_slot[i].fifo_data_out[1..5].try_into().unwrap()); 139 | } else { 140 | let compression_type = Self::get_compression_type(&tag); 141 | let sensor_type = Self::get_sensor_type(&tag); 142 | 143 | match compression_type { 144 | CompressionType::Nc => { 145 | if tag == Tag::Empty { 146 | continue; 147 | } 148 | 149 | if tag == Tag::StepCounter || tag == Tag::MlcResult { 150 | fifo_out_slot[j].timestamp = u32::from_le_bytes(fifo_raw_slot[i].fifo_data_out[3..7].try_into().unwrap()); 151 | } else { 152 | fifo_out_slot[j].timestamp = self.timestamp; 153 | } 154 | 155 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 156 | fifo_out_slot[j].sensor_data.from_u8_arr(&fifo_raw_slot[i].fifo_data_out[1..7]); 157 | 158 | if sensor_type == SensorType::Accelerometer { 159 | self.last_data_xl = fifo_out_slot[j].sensor_data.data; 160 | self.last_timestamp_xl = self.timestamp; 161 | self.bdr_chg_xl_flag = 0; 162 | } 163 | 164 | if sensor_type == SensorType::Gyroscope { 165 | self.last_data_gy = fifo_out_slot[j].sensor_data.data; 166 | self.last_timestamp_gy = self.timestamp; 167 | self.bdr_chg_gy_flag = 0; 168 | } 169 | 170 | j += 1; 171 | } 172 | CompressionType::NcT1 => { 173 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 174 | fifo_out_slot[j].sensor_data.from_u8_arr(&fifo_raw_slot[i].fifo_data_out[1..7]); 175 | 176 | if sensor_type == SensorType::Accelerometer { 177 | let last_timestamp = if self.bdr_chg_xl_flag == 1 { 178 | self.last_timestamp_xl + self.dtime_xl_old 179 | } else { 180 | self.timestamp - self.dtime_xl 181 | }; 182 | 183 | fifo_out_slot[j].timestamp = last_timestamp; 184 | self.last_data_xl = fifo_out_slot[j].sensor_data.data; 185 | self.last_timestamp_xl = last_timestamp; 186 | } 187 | 188 | if sensor_type == SensorType::Gyroscope { 189 | let last_timestamp = if self.bdr_chg_gy_flag == 1 { 190 | self.last_timestamp_gy + self.dtime_gy_old 191 | } else { 192 | self.timestamp - self.dtime_gy 193 | }; 194 | 195 | fifo_out_slot[j].timestamp = last_timestamp; 196 | self.last_data_gy = fifo_out_slot[j].sensor_data.data; 197 | self.last_timestamp_gy = last_timestamp; 198 | } 199 | 200 | j += 1; 201 | } 202 | CompressionType::NcT2 => { 203 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 204 | fifo_out_slot[j].sensor_data.from_u8_arr(&fifo_raw_slot[i].fifo_data_out[1..7]); 205 | 206 | if sensor_type == SensorType::Accelerometer { 207 | let last_timestamp = if self.bdr_chg_xl_flag == 1 { 208 | self.last_timestamp_xl + self.dtime_xl_old 209 | } else { 210 | self.timestamp - 2 * self.dtime_xl 211 | }; 212 | 213 | fifo_out_slot[j].timestamp = last_timestamp; 214 | self.last_data_xl = fifo_out_slot[j].sensor_data.data; 215 | self.last_timestamp_xl = last_timestamp; 216 | } 217 | 218 | if sensor_type == SensorType::Gyroscope { 219 | let last_timestamp = if self.bdr_chg_gy_flag == 1 { 220 | self.last_timestamp_gy + self.dtime_gy_old 221 | } else { 222 | self.timestamp - 2 * self.dtime_gy 223 | }; 224 | 225 | fifo_out_slot[j].timestamp = last_timestamp; 226 | self.last_data_gy = fifo_out_slot[j].sensor_data.data; 227 | self.last_timestamp_gy = last_timestamp; 228 | } 229 | 230 | j += 1; 231 | } 232 | CompressionType::Comp2x => { 233 | let mut diff = [0i16; 6]; 234 | FifoData::get_diff_2x(&mut diff, &fifo_raw_slot[i].fifo_data_out[1..7]); 235 | 236 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 237 | 238 | if sensor_type == SensorType::Accelerometer { 239 | let data = [ 240 | self.last_data_xl[0] + diff[0], 241 | self.last_data_xl[1] + diff[1], 242 | self.last_data_xl[2] + diff[2], 243 | ]; 244 | fifo_out_slot[j].timestamp = self.timestamp - 2 * self.dtime_xl; 245 | self.last_data_xl = data; 246 | fifo_out_slot[j].sensor_data.data = data; 247 | } 248 | 249 | if sensor_type == SensorType::Gyroscope { 250 | let data =[ 251 | self.last_data_gy[0] + diff[0], 252 | self.last_data_gy[1] + diff[1], 253 | self.last_data_gy[2] + diff[2], 254 | ]; 255 | fifo_out_slot[j].timestamp = self.timestamp - 2 * self.dtime_gy; 256 | self.last_data_gy = data; 257 | fifo_out_slot[j].sensor_data.data = data 258 | } 259 | 260 | j += 1; 261 | 262 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 263 | 264 | if sensor_type == SensorType::Accelerometer { 265 | let last_timestamp = self.timestamp - self.dtime_xl; 266 | let data = [ 267 | self.last_data_xl[0] + diff[3], 268 | self.last_data_xl[1] + diff[4], 269 | self.last_data_xl[2] + diff[5], 270 | ]; 271 | fifo_out_slot[j].timestamp = last_timestamp; 272 | self.last_data_xl = data; 273 | fifo_out_slot[j].sensor_data.data = data; 274 | self.last_timestamp_xl = last_timestamp; 275 | } 276 | 277 | if sensor_type == SensorType::Gyroscope { 278 | let last_timestamp = self.timestamp - self.dtime_gy; 279 | let data = [ 280 | self.last_data_gy[0] + diff[3], 281 | self.last_data_gy[1] + diff[4], 282 | self.last_data_gy[2] + diff[5], 283 | ]; 284 | fifo_out_slot[j].timestamp = last_timestamp; 285 | self.last_data_gy = data; 286 | fifo_out_slot[j].sensor_data.data = data; 287 | self.last_timestamp_gy = last_timestamp; 288 | } 289 | 290 | j += 1; 291 | } 292 | CompressionType::Comp3x => { 293 | let mut diff = [0i16; 9]; 294 | Self::get_diff_3x(&mut diff, &fifo_raw_slot[i].fifo_data_out[1..7]); 295 | 296 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 297 | 298 | if sensor_type == SensorType::Accelerometer { 299 | let data = [ 300 | self.last_data_xl[0] + diff[0], 301 | self.last_data_xl[1] + diff[1], 302 | self.last_data_xl[2] + diff[2], 303 | ]; 304 | fifo_out_slot[j].timestamp = self.timestamp - 2 * self.dtime_xl; 305 | self.last_data_xl = data; 306 | fifo_out_slot[j].sensor_data.data = data 307 | } 308 | 309 | if sensor_type == SensorType::Gyroscope { 310 | let data = [ 311 | self.last_data_gy[0] + diff[0], 312 | self.last_data_gy[1] + diff[1], 313 | self.last_data_gy[2] + diff[2], 314 | ]; 315 | fifo_out_slot[j].timestamp = self.timestamp - 2 * self.dtime_gy; 316 | self.last_data_gy = data; 317 | fifo_out_slot[j].sensor_data.data = data; 318 | } 319 | 320 | j += 1; 321 | 322 | 323 | if sensor_type == SensorType::Accelerometer { 324 | let data = [ 325 | self.last_data_xl[0] + diff[3], 326 | self.last_data_xl[1] + diff[4], 327 | self.last_data_xl[2] + diff[5], 328 | ]; 329 | fifo_out_slot[j].sensor_data.data = data; 330 | self.last_data_xl = data; 331 | fifo_out_slot[j].timestamp = self.timestamp - self.dtime_xl; 332 | self.last_timestamp_xl = self.timestamp; 333 | } 334 | 335 | if sensor_type == SensorType::Gyroscope { 336 | let data = [ 337 | self.last_data_gy[0] + diff[3], 338 | self.last_data_gy[1] + diff[4], 339 | self.last_data_gy[2] + diff[5], 340 | ]; 341 | fifo_out_slot[j].sensor_data.data = data; 342 | self.last_data_gy = data; 343 | fifo_out_slot[j].timestamp = self.timestamp - self.dtime_gy; 344 | self.last_timestamp_gy = self.timestamp; 345 | } 346 | 347 | j += 1; 348 | 349 | fifo_out_slot[j].timestamp = self.timestamp; 350 | fifo_out_slot[j].sensor_tag = sensor_type.clone(); 351 | 352 | if sensor_type == SensorType::Accelerometer { 353 | let data = [ 354 | self.last_data_xl[0] + diff[6], 355 | self.last_data_xl[1] + diff[7], 356 | self.last_data_xl[2] + diff[8], 357 | ]; 358 | self.last_data_xl = data; 359 | fifo_out_slot[j].sensor_data.data = data; 360 | self.last_timestamp_xl = self.timestamp; 361 | } 362 | 363 | if sensor_type == SensorType::Gyroscope { 364 | let data = [ 365 | self.last_data_gy[0] + diff[6], 366 | self.last_data_gy[1] + diff[7], 367 | self.last_data_gy[2] + diff[8], 368 | ]; 369 | self.last_data_gy = data; 370 | fifo_out_slot[j].sensor_data.data = data; 371 | self.last_timestamp_gy = self.timestamp; 372 | } 373 | 374 | j += 1; 375 | } 376 | } 377 | 378 | *out_slot_size = j as u16; 379 | } 380 | 381 | self.tag_counter_old = tag_counter; 382 | } 383 | 384 | Status::Ok 385 | } 386 | pub fn bytes_to_i16_array(source_bytes: &[u8; 6], destination: &mut [i16; 3]) { 387 | for (i, chunk) in source_bytes.chunks_exact(2).enumerate() { 388 | destination[i] = FifoData::combine_bytes_to_i16(chunk[0], chunk[1]); 389 | } 390 | } 391 | 392 | pub fn combine_bytes_to_i16(low_byte: u8, high_byte: u8) -> i16 { 393 | (((low_byte as u16) << 8) | high_byte as u16) as i16 394 | } 395 | 396 | 397 | fn is_tag_valid(&self, tag: u8) -> bool { 398 | tag <= DEVICES[self.fifo_ver as usize].tag_valid_limit 399 | } 400 | 401 | fn get_sensor_type(tag: &Tag) -> SensorType { 402 | match tag { 403 | Tag::Gy => SensorType::Gyroscope, 404 | Tag::Xl => SensorType::Accelerometer, 405 | Tag::Temp => SensorType::Temperature, 406 | Tag::ExtSens0 => SensorType::ExtSensor0, 407 | Tag::ExtSens1 => SensorType::ExtSensor1, 408 | Tag::ExtSens2 => SensorType::ExtSensor2, 409 | Tag::ExtSens3 => SensorType::ExtSensor3, 410 | Tag::XlUncompressedT2 => SensorType::Accelerometer, 411 | Tag::XlUncompressedT1 => SensorType::Accelerometer, 412 | Tag::XlCompressed2x => SensorType::Accelerometer, 413 | Tag::XlCompressed3x => SensorType::Accelerometer, 414 | Tag::GyUncompressedT2 => SensorType::Gyroscope, 415 | Tag::GyUncompressedT1 => SensorType::Gyroscope, 416 | Tag::GyCompressed2x => SensorType::Gyroscope, 417 | Tag::GyCompressed3x => SensorType::Gyroscope, 418 | Tag::StepCounter => SensorType::StepCounter, 419 | Tag::GameRv => SensorType::GameRv6x, 420 | Tag::GeomRv => SensorType::GeomRv6x, 421 | Tag::NormRv => SensorType::Rv9x, 422 | Tag::GyroBias => SensorType::GyroBias, 423 | Tag::Gravity => SensorType::Gravity, 424 | Tag::MagCal => SensorType::MagCalib, 425 | Tag::ExtSensNack => SensorType::ExtSensorNack, 426 | Tag::MlcResult => SensorType::MlcResult, 427 | Tag::MlcFilter => SensorType::MlcFilter, 428 | Tag::MlcFeature => SensorType::MlcFeature, 429 | Tag::DualcXl => SensorType::DualAccel, 430 | Tag::EisGy => SensorType::EisGyro, 431 | _ => SensorType::None, 432 | } 433 | } 434 | 435 | fn get_compression_type(tag: &Tag) -> CompressionType { 436 | match tag { 437 | Tag::XlUncompressedT2 | Tag::GyUncompressedT2 => CompressionType::NcT2, 438 | Tag::XlUncompressedT1 | Tag::GyUncompressedT1 => CompressionType::NcT1, 439 | Tag::XlCompressed2x | Tag::GyCompressed2x => CompressionType::Comp2x, 440 | Tag::XlCompressed3x | Tag::GyCompressed3x => CompressionType::Comp3x, 441 | _ => CompressionType::Nc, 442 | } 443 | } 444 | 445 | fn bdr_get_index(bdr: &[f32; 16], n: f32) -> usize { 446 | let mut min_diff = f32::MAX; 447 | let mut idx = 0; 448 | 449 | for (i, &value) in bdr.iter().enumerate() { 450 | let diff = (value - n).abs(); 451 | if diff < min_diff { 452 | min_diff = diff; 453 | idx = i; 454 | } 455 | } 456 | 457 | idx 458 | } 459 | 460 | fn has_even_parity(x: u8) -> bool { 461 | x.count_ones() % 2 == 0 462 | } 463 | 464 | fn get_diff_2x(diff: &mut [i16; 6], input: &[u8]) { 465 | for (i, &byte) in input.iter().enumerate() { 466 | diff[i] = if byte < 128 { byte as i16 } else { byte as i16 - 256 }; 467 | } 468 | } 469 | 470 | fn get_diff_3x(diff: &mut [i16; 9], input: &[u8]) { 471 | for i in 0..3 { 472 | let decode_tmp = u16::from_le_bytes([input[2 * i], input[2 * i + 1]]); 473 | for j in 0..3 { 474 | let utmp = (decode_tmp >> (5 * j)) & 0x1F; 475 | let tmp = utmp as i16; 476 | diff[j + 3 * i] = if tmp < 16 { tmp } else { tmp - 32 }; 477 | } 478 | } 479 | } 480 | 481 | 482 | pub fn sort(&self, fifo_out_slot: &mut [OutSlot], out_slot_size: u16) { 483 | for i in 1..out_slot_size as usize { 484 | let temp = fifo_out_slot[i].clone(); 485 | let mut j: i32 = i as i32 - 1; 486 | 487 | while j >= 0 && fifo_out_slot[j as usize].timestamp > temp.timestamp { 488 | fifo_out_slot[(j + 1) as usize] = fifo_out_slot[j as usize].clone(); 489 | j -= 1; 490 | } 491 | 492 | fifo_out_slot[(j + 1) as usize] = temp; 493 | } 494 | } 495 | 496 | pub fn get_sensor_occurrence(&self, fifo_out_slot: &[OutSlot], out_slot_size: u16, sensor_type: SensorType) -> u16 { 497 | let mut occurrence = 0; 498 | 499 | for i in 0..out_slot_size as usize { 500 | if fifo_out_slot[i].sensor_tag == sensor_type { 501 | occurrence += 1; 502 | } 503 | } 504 | 505 | occurrence 506 | } 507 | 508 | pub fn extract_sensor(&self, sensor_out_slot: &mut [OutSlot], fifo_out_slot: &[OutSlot], out_slot_size: u16, sensor_type: SensorType) { 509 | let mut temp_i = 0; 510 | 511 | for i in 0..out_slot_size as usize { 512 | if fifo_out_slot[i].sensor_tag == sensor_type { 513 | sensor_out_slot[temp_i] = fifo_out_slot[i].clone(); 514 | temp_i += 1; 515 | } 516 | } 517 | } 518 | 519 | } 520 | 521 | 522 | 523 | #[repr(u8)] 524 | pub enum CompressionType { 525 | Nc, 526 | NcT1, 527 | NcT2, 528 | Comp2x, 529 | Comp3x, 530 | } 531 | 532 | pub struct Device { 533 | pub bdr_acc: [f32; 16], 534 | pub bdr_gyr: [f32; 16], 535 | pub bdr_vsens: [f32; 16], 536 | pub dtime: [u32; 16], 537 | pub tag_valid_limit: u8, 538 | } 539 | 540 | #[derive(PartialEq)] 541 | pub enum BdrMask { 542 | Xl, 543 | Gy, 544 | Vsens, 545 | } 546 | 547 | impl From for u8 { 548 | fn from(value: BdrMask) -> Self { 549 | match value { 550 | BdrMask::Xl => 0x0F, 551 | BdrMask::Gy => 0xF0, 552 | BdrMask::Vsens => 0x0F, 553 | } 554 | } 555 | } 556 | 557 | #[derive(PartialEq)] 558 | pub enum BdrShift { 559 | Xl, 560 | Gy, 561 | Vsens, 562 | } 563 | 564 | impl From for u8 { 565 | fn from(value: BdrShift) -> Self { 566 | match value { 567 | BdrShift::Xl => 0x00, 568 | BdrShift::Gy => 0x04, 569 | BdrShift::Vsens => 0x00, 570 | } 571 | } 572 | } 573 | 574 | #[derive(PartialEq)] 575 | #[repr(u8)] 576 | pub enum TagMask { 577 | Counter = 0x06, 578 | Sensor = 0xF8, 579 | } 580 | 581 | #[derive(PartialEq)] 582 | #[repr(u8)] 583 | pub enum TagShift { 584 | Counter = 0x01, 585 | Sensor = 0x03, 586 | } 587 | 588 | #[derive(PartialEq, Debug)] 589 | #[repr(u8)] 590 | pub enum Status { 591 | Ok = 0, 592 | Err = 1, 593 | } 594 | 595 | 596 | #[derive(PartialEq, Clone)] 597 | #[repr(u8)] 598 | pub enum Tag { 599 | Empty = 0x00, 600 | Gy = 0x01, 601 | Xl = 0x02, 602 | Temp = 0x03, 603 | Ts = 0x04, 604 | Odrchg = 0x05, 605 | XlUncompressedT2 = 0x06, 606 | XlUncompressedT1 = 0x07, 607 | XlCompressed2x = 0x08, 608 | XlCompressed3x = 0x09, 609 | GyUncompressedT2 = 0x0A, 610 | GyUncompressedT1 = 0x0B, 611 | GyCompressed2x = 0x0C, 612 | GyCompressed3x = 0x0D, 613 | ExtSens0 = 0x0E, 614 | ExtSens1 = 0x0F, 615 | ExtSens2 = 0x10, 616 | ExtSens3 = 0x11, 617 | StepCounter = 0x12, 618 | GameRv = 0x13, 619 | GeomRv = 0x14, 620 | NormRv = 0x15, 621 | GyroBias = 0x16, 622 | Gravity = 0x17, 623 | MagCal = 0x18, 624 | ExtSensNack = 0x19, 625 | MlcResult = 0x1A, 626 | MlcFilter = 0x1B, 627 | MlcFeature = 0x1C, 628 | DualcXl = 0x1D, 629 | EisGy = 0x1E, 630 | } 631 | 632 | impl TryFrom for Tag { 633 | type Error = (); 634 | 635 | fn try_from(value: u8) -> Result { 636 | match value { 637 | 0x00 => Ok(Tag::Empty), 638 | 0x01 => Ok(Tag::Gy), 639 | 0x02 => Ok(Tag::Xl), 640 | 0x03 => Ok(Tag::Temp), 641 | 0x04 => Ok(Tag::Ts), 642 | 0x05 => Ok(Tag::Odrchg), 643 | 0x06 => Ok(Tag::XlUncompressedT2), 644 | 0x07 => Ok(Tag::XlUncompressedT1), 645 | 0x08 => Ok(Tag::XlCompressed2x), 646 | 0x09 => Ok(Tag::XlCompressed3x), 647 | 0x0A => Ok(Tag::GyUncompressedT2), 648 | 0x0B => Ok(Tag::GyUncompressedT1), 649 | 0x0C => Ok(Tag::GyCompressed2x), 650 | 0x0D => Ok(Tag::GyCompressed3x), 651 | 0x0E => Ok(Tag::ExtSens0), 652 | 0x0F => Ok(Tag::ExtSens1), 653 | 0x10 => Ok(Tag::ExtSens2), 654 | 0x11 => Ok(Tag::ExtSens3), 655 | 0x12 => Ok(Tag::StepCounter), 656 | 0x13 => Ok(Tag::GameRv), 657 | 0x14 => Ok(Tag::GeomRv), 658 | 0x15 => Ok(Tag::NormRv), 659 | 0x16 => Ok(Tag::GyroBias), 660 | 0x17 => Ok(Tag::Gravity), 661 | 0x18 => Ok(Tag::MagCal), 662 | 0x19 => Ok(Tag::ExtSensNack), 663 | 0x1A => Ok(Tag::MlcResult), 664 | 0x1B => Ok(Tag::MlcFilter), 665 | 0x1C => Ok(Tag::MlcFeature), 666 | 0x1D => Ok(Tag::DualcXl), 667 | 0x1E => Ok(Tag::EisGy), 668 | _ => Err(()), // Return an error for unknown values 669 | } 670 | } 671 | } 672 | 673 | #[derive(PartialEq, Clone, Copy, Default)] 674 | #[repr(u8)] 675 | pub enum SensorType { 676 | #[default] Gyroscope = 0, 677 | Accelerometer = 1, 678 | Temperature = 2, 679 | ExtSensor0 = 3, 680 | ExtSensor1 = 4, 681 | ExtSensor2 = 5, 682 | ExtSensor3 = 6, 683 | StepCounter = 7, 684 | GameRv6x = 8, 685 | GeomRv6x = 9, 686 | Rv9x = 10, 687 | GyroBias = 11, 688 | Gravity = 12, 689 | MagCalib = 13, 690 | ExtSensorNack = 14, 691 | MlcResult = 15, 692 | MlcFilter = 16, 693 | MlcFeature = 17, 694 | DualAccel = 18, 695 | EisGyro = 19, 696 | None = 20, 697 | } 698 | 699 | impl fmt::Display for SensorType { 700 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 701 | let name = match self { 702 | SensorType::Gyroscope => "Gyroscope", 703 | SensorType::Accelerometer => "Accelerometer", 704 | SensorType::Temperature => "Temperature", 705 | SensorType::ExtSensor0 => "ExtSensor0", 706 | SensorType::ExtSensor1 => "ExtSensor1", 707 | SensorType::ExtSensor2 => "ExtSensor2", 708 | SensorType::ExtSensor3 => "ExtSensor3", 709 | SensorType::StepCounter => "StepCounter", 710 | SensorType::GameRv6x => "GameRv6x", 711 | SensorType::GeomRv6x => "GeomRv6x", 712 | SensorType::Rv9x => "Rv9x", 713 | SensorType::GyroBias => "GyroBias", 714 | SensorType::Gravity => "Gravity", 715 | SensorType::MagCalib => "MagCalib", 716 | SensorType::ExtSensorNack => "ExtSensorNack", 717 | SensorType::MlcResult => "MlcResult", 718 | SensorType::MlcFilter => "MlcFilter", 719 | SensorType::MlcFeature => "MlcFeature", 720 | SensorType::DualAccel => "DualAccel", 721 | SensorType::EisGyro => "EisGyro", 722 | SensorType::None => "None", 723 | }; 724 | write!(f, "{}", name) 725 | } 726 | } 727 | 728 | #[derive(Default, Copy, Clone)] 729 | pub struct RawSlot { 730 | pub fifo_data_out: [u8; 7], // registers from mems (78h -> 7Dh) 731 | } 732 | 733 | #[derive(Clone, Copy, Default)] 734 | pub struct OutSlot { 735 | pub timestamp: u32, 736 | pub sensor_tag: SensorType, 737 | pub sensor_data: SensorData, 738 | } 739 | 740 | #[derive(PartialEq, Eq, PartialOrd, Ord)] 741 | #[repr(u8)] 742 | pub enum DeviceType { 743 | Lsm6dsr = 0, 744 | Lsm6dsrx = 1, 745 | Asm330lhh = 2, 746 | Asm330lhhx = 3, 747 | Ism330dhcx = 4, 748 | Lsm6dso = 5, 749 | Lsm6dsox = 6, 750 | Lsm6dso32 = 7, 751 | Lsm6dso32x = 8, 752 | Lsm6dsv = 9, 753 | Lsm6dsv16x = 10, 754 | Lsm6dsv32x = 11, 755 | } 756 | 757 | pub struct Config { 758 | pub device: DeviceType, // device to select 759 | pub bdr_xl: f32, // accelerometer batch data rate in Hz 760 | pub bdr_gy: f32, // gyroscope batch data rate in Hz 761 | pub bdr_vsens: f32, // virtual sensor batch data rate in Hz 762 | } 763 | --------------------------------------------------------------------------------