From ac4fb1416985c150a3c6ab32329b8cee897d5602 Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Tue, 14 Oct 2025 01:01:34 -0700 Subject: [PATCH 1/7] Print GPIO pin configuration for `humility gpio --input --with-config` $ humility gpio --input -with-config --pins B:0,B:14,E:1 humility: attached to 0483:3754:002F00174741500820383733 via ST-Link V3 B:0 = 0 Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero B:14 = 1 Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero E:1 = 0 Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero ``` If no pins are specified, then the pin names, values and config settings are printeed one per line for all GPIO pins. --- Cargo.lock | 135 ++++++++++++++- Cargo.toml | 1 + cmd/gpio/Cargo.toml | 3 + cmd/gpio/src/lib.rs | 372 ++++++++++++++++++++++++++++++++++------ humility-bin/Cargo.toml | 1 + 5 files changed, 456 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 527dd9ca..5edfd858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,21 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + [[package]] name = "base64" version = "0.13.1" @@ -382,7 +397,7 @@ checksum = "7714a157da7991e23d90686b9524b9e12e0407a108647f52e9328f4b3d51ac7f" dependencies = [ "cargo-platform", "semver 0.11.0", - "semver-parser", + "semver-parser 0.10.2", "serde", "serde_json", ] @@ -596,6 +611,38 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "cpp_demangle" version = "0.4.4" @@ -923,7 +970,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.0", "syn 2.0.15", ] @@ -950,6 +997,16 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1177,7 +1234,7 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version", + "rustc_version 0.4.0", "serde", "spin", "stable_deref_trait", @@ -1366,6 +1423,7 @@ dependencies = [ "indexmap 1.9.3", "indicatif", "jep106", + "lazy_static", "log", "multimap", "num-derive 0.4.2", @@ -1641,7 +1699,10 @@ dependencies = [ "humility-cli", "humility-cmd", "humility-hiffy", + "lazy_static", "parse_int", + "stm32h7", + "zerocopy", ] [[package]] @@ -2924,6 +2985,21 @@ dependencies = [ "serde", ] +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "nix" version = "0.26.2" @@ -3571,6 +3647,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -3662,13 +3747,22 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser", + "semver-parser 0.10.2", "serde", ] @@ -3678,6 +3772,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "semver-parser" version = "0.10.2" @@ -3926,6 +4026,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stm32h7" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0faa648e03579befdd7267ab5c669624729028001fcf3c973832f53e310a06" +dependencies = [ + "bare-metal 1.0.0", + "cortex-m", + "cortex-m-rt", + "vcell", +] + [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -4280,6 +4392,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4298,6 +4416,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + [[package]] name = "vsc7448-info" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 74819425..7a9cdc35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ tlvc-text = {git = "https://github.com/oxidecomputer/tlvc"} vsc7448-info = { git = "https://github.com/oxidecomputer/vsc7448.git" } vsc7448-types = { git = "https://github.com/oxidecomputer/vsc7448.git" } ipcc-data = { git = "https://github.com/oxidecomputer/ipcc-rs" } +stm32h7 = { version = "0.14", default-features = false } # # We depend on the oxide-stable branch of Oxide's fork of probe-rs to assure diff --git a/cmd/gpio/Cargo.toml b/cmd/gpio/Cargo.toml index 24a58509..ff398c91 100644 --- a/cmd/gpio/Cargo.toml +++ b/cmd/gpio/Cargo.toml @@ -9,6 +9,9 @@ hif.workspace = true clap.workspace = true anyhow.workspace = true parse_int.workspace = true +lazy_static.workspace = true +stm32h7 = { workspace = true, features = ["rt", "stm32h753"] } +zerocopy.workspace = true humility-cli.workspace = true humility-cmd.workspace = true diff --git a/cmd/gpio/src/lib.rs b/cmd/gpio/src/lib.rs index 29c580e0..efebd1f5 100644 --- a/cmd/gpio/src/lib.rs +++ b/cmd/gpio/src/lib.rs @@ -73,6 +73,19 @@ //! Port J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! Port K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! ``` +//! To get input values with GPIO configuration settings, +//! use the --with-config or -w flag with --input: +//! +//! ```console +//! $ humility gpio --input -with-config --pins B:0,B:14,E:1 +//! humility: attached to 0483:3754:002F00174741500820383733 via ST-Link V3 +//! B:0 = 0 Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero +//! B:14 = 1 Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero +//! E:1 = 0 Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero +//! ``` +//! +//! If no pins are specified, then the pin names, values and config settings +//! are printeed one per line for all GPIO pins. //! //! ### Configure //! @@ -94,10 +107,11 @@ use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; -use humility_hiffy::HiffyContext; +use humility_hiffy::{HiffyContext, IpcError}; +use std::mem::MaybeUninit; use std::str; -use anyhow::{Result, bail}; +use anyhow::{anyhow, bail, Result}; use clap::{CommandFactory, Parser}; use hif::*; @@ -120,6 +134,10 @@ struct GpioArgs { )] input: bool, + /// show configuration of pins along with their values + #[clap(short, long, requires = "input")] + with_config: bool, + /// toggle specified pins #[clap( long, short, requires = "pins", @@ -149,18 +167,263 @@ struct GpioArgs { pins: Option>, } +use device::gpioa::RegisterBlock; +use humility_hiffy::HiffyFunction; +use lazy_static::lazy_static; +use std::collections::BTreeMap; +use std::ops::RangeInclusive; +use stm32h7::stm32h753 as device; + +lazy_static! { + static ref GPIO_REGISTERS: BTreeMap<&'static str, RangeInclusive> = { + let mut map = BTreeMap::new(); + // Section 11.4: GPIO registers + map.insert("A", 0x58020000..=0x580203FF); + map.insert("B", 0x58020400..=0x580207FF); + map.insert("C", 0x58020800..=0x58020BFF); + map.insert("D", 0x58020C00..=0x58020FFF); + map.insert("E", 0x58021000..=0x580213FF); + map.insert("F", 0x58021400..=0x580217FF); + map.insert("G", 0x58021800..=0x58021BFF); + map.insert("H", 0x58021C00..=0x58021FFF); + map.insert("I", 0x58022000..=0x580223FF); + map.insert("J", 0x58022400..=0x580227FF); + map.insert("K", 0x58022800..=0x58022BFF); + map + }; +} + +// This is a hack. +struct PinConfig { + mode: String, + otype: String, + speed: String, + pull: String, + input: String, + output: String, + lock: String, + alternate: String, +} + +use std::fmt; + +impl fmt::Display for PinConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Use the write! macro to format the output to the formatter 'f' + write!( + f, + "{}:{}:{}:{}:{}:{}:{}:{}", + self.mode, + self.otype, + self.speed, + self.pull, + self.alternate, + self.lock, + self.input, + self.output + ) + } +} + +struct ConfigCache { + cache: BTreeMap, +} + +impl ConfigCache { + fn new() -> ConfigCache { + let cache: BTreeMap = BTreeMap::new(); + Self { cache } + } + + fn get_pin_config( + &mut self, + context: &mut ExecutionContext, + group: &str, + pin: u32, + ) -> Result { + let rb = match self.cache.get(group) { + None => { + let rb = read_gpio_register_block(context, group)?; + self.cache.insert(group.to_string(), rb); + self.cache.get(group).unwrap() + } + Some(rb) => rb, + }; + + let reg = rb.moder.read().bits(); + let mode = match (reg >> (pin * 2)) & 3 { + 0 => "Input", + 1 => "Output", + 2 => "Alternate", + 3 => "Analog", + _ => unreachable!(), + }; + + let reg = rb.otyper.read().bits(); + let otype = match (reg >> pin) & 1 { + 0 => "OutPushPull", + 1 => "OutOpenDrain", + _ => unreachable!(), + }; + + let reg = rb.ospeedr.read().bits(); + let speed = match (reg >> (pin * 2)) & 3 { + 0 => "LowSpeed", + 1 => "MediumSpeed", + 2 => "HighSpeed", + 3 => "VeryHighSpeed", + _ => unreachable!(), + }; + + let reg = rb.pupdr.read().bits(); + let pull = match (reg >> (pin * 2)) & 3 { + 0 => "NoPull", + 1 => "PullUp", + 2 => "PullDown", + 3 => "ReservedPull", + _ => unreachable!(), + }; + + let reg = rb.idr.read().bits(); + let input = match (reg >> pin) & 1 { + 0 => "InZero", + 1 => "InOne", + _ => unreachable!(), + }; + + let reg = rb.odr.read().bits(); + let output = match (reg >> pin) & 1 { + 0 => "OutZero", + 1 => "OutOne", + _ => unreachable!(), + }; + + // Note: Bit 16 indicates that the register is locked or unlocked. + let reg = rb.lckr.read().bits(); + let lock = match (reg >> pin) & 1 { + 0 => "Unlocked", + 1 => "Locked", + _ => unreachable!(), + }; + + let alternate = format!( + "AF{}", + if pin < 8 { + let reg = rb.afrl.read().bits(); + (reg >> (pin * 4)) & 15 + } else { + let reg = rb.afrh.read().bits(); + (reg >> ((pin - 8) * 4)) & 15 + } + ); + + Ok(PinConfig { + mode: mode.to_string(), + otype: otype.to_string(), + speed: speed.to_string(), + pull: pull.to_string(), + input: input.to_string(), + output: output.to_string(), + lock: lock.to_string(), + alternate, + }) + } +} + +fn read_gpio_register_block( + context: &mut ExecutionContext, + group: &str, +) -> Result { + let core = &mut **context.core.as_mut().unwrap(); + let span = GPIO_REGISTERS + .get(group) + .ok_or(anyhow!("no address for GPIO group {}", group))?; + let mut buffer = vec![0u8; std::mem::size_of::()]; + core.read_8(*span.start(), &mut buffer)?; + + let mut rb: MaybeUninit = MaybeUninit::uninit(); + // let ptr: *mut u8 = rb.as_mut_ptr() as *mut u8; + unsafe { + // Get a mutable pointer to the allocated memory + let dest_ptr = rb.as_mut_ptr() as *mut u8; + + // Copy the raw bytes into the struct's memory location + // Ensure the size of raw_bytes matches the size of MyStruct + // to avoid out-of-bounds access. + std::ptr::copy_nonoverlapping(buffer.as_ptr(), dest_ptr, buffer.len()); + + Ok(rb.assume_init()) + } +} + +fn show_gpio_with_config( + context: &mut ExecutionContext, + gpio_input: &HiffyFunction, + args: &[(u16, Option, String)], + results: &[Result, IpcError>], +) -> Result<()> { + let mut config_cache = ConfigCache::new(); + for (ndx, arg) in args.iter().enumerate() { + match arg.1 { + Some(pin) => { + let config = + config_cache.get_pin_config(context, &arg.2, pin as u32)?; + println!( + "{}:{:<2} = {} {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + }, + config + ); + } + + None => match results[ndx] { + Err(code) => { + println!("Port {}: {}", arg.2, gpio_input.strerror(code)) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + + for i in 0..16 { + let config = config_cache + .get_pin_config(context, &arg.2, i as u32)?; + println!( + "{}:{:<2} = {} ({})", + arg.2, + i, + (v >> i) & 1, + config + ); + } + } + }, + } + } + Ok(()) +} + fn gpio(context: &mut ExecutionContext) -> Result<()> { let core = &mut **context.core.as_mut().unwrap(); let Subcommand::Other(subargs) = context.cli.cmd.as_ref().unwrap(); let hubris = context.archive.as_ref().unwrap(); let subargs = GpioArgs::try_parse_from(subargs)?; - let mut context = HiffyContext::new(hubris, core, subargs.timeout)?; + let mut hiffy_context = HiffyContext::new(hubris, core, subargs.timeout)?; - let gpio_toggle = context.get_function("GpioToggle", 2)?; - let gpio_set = context.get_function("GpioSet", 2)?; - let gpio_reset = context.get_function("GpioReset", 2)?; - let gpio_input = context.get_function("GpioInput", 1)?; - let gpio_configure = context.get_function("GpioConfigure", 7)?; + let gpio_toggle = hiffy_context.get_function("GpioToggle", 2)?; + let gpio_set = hiffy_context.get_function("GpioSet", 2)?; + let gpio_reset = hiffy_context.get_function("GpioReset", 2)?; + let gpio_input = hiffy_context.get_function("GpioInput", 1)?; + let gpio_configure = hiffy_context.get_function("GpioConfigure", 7)?; let mut configure_args = vec![]; let target = if subargs.toggle { @@ -257,64 +520,69 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> { ops.push(Op::Done); - let results = context.run(core, ops.as_slice(), None)?; + let results = hiffy_context.run(core, ops.as_slice(), None)?; if subargs.input { let mut header = false; - for (ndx, arg) in args.iter().enumerate() { - match arg.1 { - Some(pin) => { - println!( - "{}:{:<2} = {}", - arg.2, - pin, - match results[ndx] { - Err(code) => { - gpio_input.strerror(code) + if subargs.with_config { + let _ = + show_gpio_with_config(context, &gpio_input, &args, &results); + } else { + for (ndx, arg) in args.iter().enumerate() { + match arg.1 { + Some(pin) => { + println!( + "{}:{:<2} = {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - format!("{}", (v >> pin) & 1) - } - } - ); - } - - None => { - if !header { - print!("{:7}", "Pin"); + ); + } - for i in 0..16 { - print!("{:4}", i); - } + None => { + if !header { + print!("{:7}", "Pin"); - println!(); + for i in 0..16 { + print!("{:4}", i); + } - print!("-------"); + println!(); - for _i in 0..16 { - print!("----"); - } + print!("-------"); - println!(); - header = true; - } + for _i in 0..16 { + print!("----"); + } - print!("Port {} ", arg.2); - match results[ndx] { - Err(code) => { - println!("{}", gpio_input.strerror(code)) + println!(); + header = true; } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - for i in 0..16 { - print!("{:4}", (v >> i) & 1) + print!("Port {} ", arg.2); + match results[ndx] { + Err(code) => { + println!("{}", gpio_input.strerror(code)) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + + for i in 0..16 { + print!("{:4}", (v >> i) & 1) + } + println!(); } - println!(); } } } diff --git a/humility-bin/Cargo.toml b/humility-bin/Cargo.toml index 00887ee3..c89ca708 100644 --- a/humility-bin/Cargo.toml +++ b/humility-bin/Cargo.toml @@ -111,6 +111,7 @@ indicatif = { workspace = true } colored = { workspace = true } indexmap = { workspace = true } reedline = { workspace = true } +lazy_static.workspace = true [dev-dependencies] trycmd = { workspace = true } From 914aa98fc0c62b3f7691f26d13cd217161d5066c Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Tue, 14 Oct 2025 12:16:43 -0700 Subject: [PATCH 2/7] Refactor STM32H753 GPIO configuration printing support to a module. Applied several suggestions from Matt. Co-authored-by: Matt Keeter --- Cargo.toml | 2 +- cmd/gpio/Cargo.toml | 2 +- cmd/gpio/src/lib.rs | 284 +++++--------------------------------- cmd/gpio/src/stm32h753.rs | 237 +++++++++++++++++++++++++++++++ 4 files changed, 270 insertions(+), 255 deletions(-) create mode 100644 cmd/gpio/src/stm32h753.rs diff --git a/Cargo.toml b/Cargo.toml index 7a9cdc35..f39ef469 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,6 @@ tlvc-text = {git = "https://github.com/oxidecomputer/tlvc"} vsc7448-info = { git = "https://github.com/oxidecomputer/vsc7448.git" } vsc7448-types = { git = "https://github.com/oxidecomputer/vsc7448.git" } ipcc-data = { git = "https://github.com/oxidecomputer/ipcc-rs" } -stm32h7 = { version = "0.14", default-features = false } # # We depend on the oxide-stable branch of Oxide's fork of probe-rs to assure @@ -248,6 +247,7 @@ serde-xml-rs = "0.5.1" sha2 = "0.10.1" splitty = "0.1.0" srec = "0.2" +stm32h7 = { version = "0.14", features = ["stm32h753"] } strum = "0.22" strum_macros = "0.22" syn = "1.0" diff --git a/cmd/gpio/Cargo.toml b/cmd/gpio/Cargo.toml index ff398c91..9b749a42 100644 --- a/cmd/gpio/Cargo.toml +++ b/cmd/gpio/Cargo.toml @@ -10,7 +10,7 @@ clap.workspace = true anyhow.workspace = true parse_int.workspace = true lazy_static.workspace = true -stm32h7 = { workspace = true, features = ["rt", "stm32h753"] } +stm32h7.workspace = true zerocopy.workspace = true humility-cli.workspace = true diff --git a/cmd/gpio/src/lib.rs b/cmd/gpio/src/lib.rs index efebd1f5..165c5d4d 100644 --- a/cmd/gpio/src/lib.rs +++ b/cmd/gpio/src/lib.rs @@ -73,8 +73,8 @@ //! Port J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! Port K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! ``` -//! To get input values with GPIO configuration settings, -//! use the --with-config or -w flag with --input: +//! To get input values with STM32H753 GPIO configuration settings, +//! use the `--with-config` or `-w` flag with --input: //! //! ```console //! $ humility gpio --input -with-config --pins B:0,B:14,E:1 @@ -85,7 +85,7 @@ //! ``` //! //! If no pins are specified, then the pin names, values and config settings -//! are printeed one per line for all GPIO pins. +//! are printed one per line for all GPIO pins. //! //! ### Configure //! @@ -105,15 +105,39 @@ //! ``` //! +mod stm32h753; + use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; -use humility_hiffy::{HiffyContext, IpcError}; -use std::mem::MaybeUninit; -use std::str; +use humility_hiffy::HiffyContext; use anyhow::{anyhow, bail, Result}; use clap::{CommandFactory, Parser}; use hif::*; +use humility_hiffy::IpcError; + +fn show_gpio_with_config( + context: &mut ExecutionContext, + gpio_input: &humility_hiffy::HiffyFunction, + args: &[(u16, Option, String)], + results: &[Result, IpcError>], +) -> Result<()> { + let hubris = context.archive.as_ref().unwrap(); + if let Some(chip) = hubris.chip() { + match chip.as_str() { + "stm32h7" => { + // Call the stm32h753-specific function + stm32h753::show_gpio_with_config(context, gpio_input, args, results) + } + _ => Err(anyhow!("GPIO `--with-config` is not supported for chip '{chip}'")), + } + } else { + Err(anyhow!( + "GPIO `--with-config` is not supported when chip is not specified" + )) + } +} + use std::convert::TryInto; @@ -167,251 +191,6 @@ struct GpioArgs { pins: Option>, } -use device::gpioa::RegisterBlock; -use humility_hiffy::HiffyFunction; -use lazy_static::lazy_static; -use std::collections::BTreeMap; -use std::ops::RangeInclusive; -use stm32h7::stm32h753 as device; - -lazy_static! { - static ref GPIO_REGISTERS: BTreeMap<&'static str, RangeInclusive> = { - let mut map = BTreeMap::new(); - // Section 11.4: GPIO registers - map.insert("A", 0x58020000..=0x580203FF); - map.insert("B", 0x58020400..=0x580207FF); - map.insert("C", 0x58020800..=0x58020BFF); - map.insert("D", 0x58020C00..=0x58020FFF); - map.insert("E", 0x58021000..=0x580213FF); - map.insert("F", 0x58021400..=0x580217FF); - map.insert("G", 0x58021800..=0x58021BFF); - map.insert("H", 0x58021C00..=0x58021FFF); - map.insert("I", 0x58022000..=0x580223FF); - map.insert("J", 0x58022400..=0x580227FF); - map.insert("K", 0x58022800..=0x58022BFF); - map - }; -} - -// This is a hack. -struct PinConfig { - mode: String, - otype: String, - speed: String, - pull: String, - input: String, - output: String, - lock: String, - alternate: String, -} - -use std::fmt; - -impl fmt::Display for PinConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Use the write! macro to format the output to the formatter 'f' - write!( - f, - "{}:{}:{}:{}:{}:{}:{}:{}", - self.mode, - self.otype, - self.speed, - self.pull, - self.alternate, - self.lock, - self.input, - self.output - ) - } -} - -struct ConfigCache { - cache: BTreeMap, -} - -impl ConfigCache { - fn new() -> ConfigCache { - let cache: BTreeMap = BTreeMap::new(); - Self { cache } - } - - fn get_pin_config( - &mut self, - context: &mut ExecutionContext, - group: &str, - pin: u32, - ) -> Result { - let rb = match self.cache.get(group) { - None => { - let rb = read_gpio_register_block(context, group)?; - self.cache.insert(group.to_string(), rb); - self.cache.get(group).unwrap() - } - Some(rb) => rb, - }; - - let reg = rb.moder.read().bits(); - let mode = match (reg >> (pin * 2)) & 3 { - 0 => "Input", - 1 => "Output", - 2 => "Alternate", - 3 => "Analog", - _ => unreachable!(), - }; - - let reg = rb.otyper.read().bits(); - let otype = match (reg >> pin) & 1 { - 0 => "OutPushPull", - 1 => "OutOpenDrain", - _ => unreachable!(), - }; - - let reg = rb.ospeedr.read().bits(); - let speed = match (reg >> (pin * 2)) & 3 { - 0 => "LowSpeed", - 1 => "MediumSpeed", - 2 => "HighSpeed", - 3 => "VeryHighSpeed", - _ => unreachable!(), - }; - - let reg = rb.pupdr.read().bits(); - let pull = match (reg >> (pin * 2)) & 3 { - 0 => "NoPull", - 1 => "PullUp", - 2 => "PullDown", - 3 => "ReservedPull", - _ => unreachable!(), - }; - - let reg = rb.idr.read().bits(); - let input = match (reg >> pin) & 1 { - 0 => "InZero", - 1 => "InOne", - _ => unreachable!(), - }; - - let reg = rb.odr.read().bits(); - let output = match (reg >> pin) & 1 { - 0 => "OutZero", - 1 => "OutOne", - _ => unreachable!(), - }; - - // Note: Bit 16 indicates that the register is locked or unlocked. - let reg = rb.lckr.read().bits(); - let lock = match (reg >> pin) & 1 { - 0 => "Unlocked", - 1 => "Locked", - _ => unreachable!(), - }; - - let alternate = format!( - "AF{}", - if pin < 8 { - let reg = rb.afrl.read().bits(); - (reg >> (pin * 4)) & 15 - } else { - let reg = rb.afrh.read().bits(); - (reg >> ((pin - 8) * 4)) & 15 - } - ); - - Ok(PinConfig { - mode: mode.to_string(), - otype: otype.to_string(), - speed: speed.to_string(), - pull: pull.to_string(), - input: input.to_string(), - output: output.to_string(), - lock: lock.to_string(), - alternate, - }) - } -} - -fn read_gpio_register_block( - context: &mut ExecutionContext, - group: &str, -) -> Result { - let core = &mut **context.core.as_mut().unwrap(); - let span = GPIO_REGISTERS - .get(group) - .ok_or(anyhow!("no address for GPIO group {}", group))?; - let mut buffer = vec![0u8; std::mem::size_of::()]; - core.read_8(*span.start(), &mut buffer)?; - - let mut rb: MaybeUninit = MaybeUninit::uninit(); - // let ptr: *mut u8 = rb.as_mut_ptr() as *mut u8; - unsafe { - // Get a mutable pointer to the allocated memory - let dest_ptr = rb.as_mut_ptr() as *mut u8; - - // Copy the raw bytes into the struct's memory location - // Ensure the size of raw_bytes matches the size of MyStruct - // to avoid out-of-bounds access. - std::ptr::copy_nonoverlapping(buffer.as_ptr(), dest_ptr, buffer.len()); - - Ok(rb.assume_init()) - } -} - -fn show_gpio_with_config( - context: &mut ExecutionContext, - gpio_input: &HiffyFunction, - args: &[(u16, Option, String)], - results: &[Result, IpcError>], -) -> Result<()> { - let mut config_cache = ConfigCache::new(); - for (ndx, arg) in args.iter().enumerate() { - match arg.1 { - Some(pin) => { - let config = - config_cache.get_pin_config(context, &arg.2, pin as u32)?; - println!( - "{}:{:<2} = {} {}", - arg.2, - pin, - match results[ndx] { - Err(code) => { - gpio_input.strerror(code) - } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - format!("{}", (v >> pin) & 1) - } - }, - config - ); - } - - None => match results[ndx] { - Err(code) => { - println!("Port {}: {}", arg.2, gpio_input.strerror(code)) - } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - - for i in 0..16 { - let config = config_cache - .get_pin_config(context, &arg.2, i as u32)?; - println!( - "{}:{:<2} = {} ({})", - arg.2, - i, - (v >> i) & 1, - config - ); - } - } - }, - } - } - Ok(()) -} - fn gpio(context: &mut ExecutionContext) -> Result<()> { let core = &mut **context.core.as_mut().unwrap(); let Subcommand::Other(subargs) = context.cli.cmd.as_ref().unwrap(); @@ -526,8 +305,7 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> { let mut header = false; if subargs.with_config { - let _ = - show_gpio_with_config(context, &gpio_input, &args, &results); + show_gpio_with_config(context, &gpio_input, &args, &results)?; } else { for (ndx, arg) in args.iter().enumerate() { match arg.1 { diff --git a/cmd/gpio/src/stm32h753.rs b/cmd/gpio/src/stm32h753.rs new file mode 100644 index 00000000..cfb63733 --- /dev/null +++ b/cmd/gpio/src/stm32h753.rs @@ -0,0 +1,237 @@ +use humility_cli::ExecutionContext; +use humility_hiffy::IpcError; +use std::mem::MaybeUninit; +use std::fmt; +use anyhow::{anyhow, Result}; + +use device::gpioa::RegisterBlock; +use humility_hiffy::HiffyFunction; +use std::collections::BTreeMap; +use stm32h7::stm32h753 as device; + +struct PinConfig { + mode: String, + otype: String, + speed: String, + pull: String, + input: String, + output: String, + lock: String, + alternate: String, +} + +impl fmt::Display for PinConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Use the write! macro to format the output to the formatter 'f' + write!( + f, + "{}:{}:{}:{}:{}:{}:{}:{}", + self.mode, + self.otype, + self.speed, + self.pull, + self.alternate, + self.lock, + self.input, + self.output + ) + } +} + +struct ConfigCache { + cache: BTreeMap, +} + +impl ConfigCache { + fn new() -> ConfigCache { + let cache: BTreeMap = BTreeMap::new(); + Self { cache } + } + + fn get_pin_config( + &mut self, + context: &mut ExecutionContext, + group: &str, + pin: u32, + ) -> Result { + let rb = match self.cache.get(group) { + None => { + let rb = read_gpio_register_block(context, group)?; + self.cache.insert(group.to_string(), rb); + self.cache.get(group).unwrap() + } + Some(rb) => rb, + }; + + let reg = rb.moder.read().bits(); + let mode = match (reg >> (pin * 2)) & 3 { + 0 => "Input", + 1 => "Output", + 2 => "Alternate", + 3 => "Analog", + _ => unreachable!(), + }; + + let reg = rb.otyper.read().bits(); + let otype = match (reg >> pin) & 1 { + 0 => "OutPushPull", + 1 => "OutOpenDrain", + _ => unreachable!(), + }; + + let reg = rb.ospeedr.read().bits(); + let speed = match (reg >> (pin * 2)) & 3 { + 0 => "LowSpeed", + 1 => "MediumSpeed", + 2 => "HighSpeed", + 3 => "VeryHighSpeed", + _ => unreachable!(), + }; + + let reg = rb.pupdr.read().bits(); + let pull = match (reg >> (pin * 2)) & 3 { + 0 => "NoPull", + 1 => "PullUp", + 2 => "PullDown", + 3 => "ReservedPull", + _ => unreachable!(), + }; + + let reg = rb.idr.read().bits(); + let input = match (reg >> pin) & 1 { + 0 => "InZero", + 1 => "InOne", + _ => unreachable!(), + }; + + let reg = rb.odr.read().bits(); + let output = match (reg >> pin) & 1 { + 0 => "OutZero", + 1 => "OutOne", + _ => unreachable!(), + }; + + // Note: Bit 16 indicates that the register is locked or unlocked. + let reg = rb.lckr.read().bits(); + let lock = match (reg >> pin) & 1 { + 0 => "Unlocked", + 1 => "Locked", + _ => unreachable!(), + }; + + let alternate = format!( + "AF{}", + if pin < 8 { + let reg = rb.afrl.read().bits(); + (reg >> (pin * 4)) & 15 + } else { + let reg = rb.afrh.read().bits(); + (reg >> ((pin - 8) * 4)) & 15 + } + ); + + Ok(PinConfig { + mode: mode.to_string(), + otype: otype.to_string(), + speed: speed.to_string(), + pull: pull.to_string(), + input: input.to_string(), + output: output.to_string(), + lock: lock.to_string(), + alternate, + }) + } +} + +fn stm32h753_gpio_registerblock_addr(group: &str) -> Option { + // Section 11.4: GPIO registers + match group { + "A" => Some(device::GPIOA::ptr() as u32), + "B" => Some(device::GPIOB::ptr() as u32), + "C" => Some(device::GPIOC::ptr() as u32), + "D" => Some(device::GPIOD::ptr() as u32), + "E" => Some(device::GPIOE::ptr() as u32), + "F" => Some(device::GPIOF::ptr() as u32), + "G" => Some(device::GPIOG::ptr() as u32), + "H" => Some(device::GPIOH::ptr() as u32), + "I" => Some(device::GPIOI::ptr() as u32), + "J" => Some(device::GPIOJ::ptr() as u32), + "K" => Some(device::GPIOK::ptr() as u32), + _ => None, + } +} + +fn read_gpio_register_block( + context: &mut ExecutionContext, + group: &str, +) -> Result { + let core = &mut **context.core.as_mut().unwrap(); + let addr = stm32h753_gpio_registerblock_addr(group).ok_or(anyhow!("no address for GPIO group {}", group))?; + let mut buffer = vec![0u8; std::mem::size_of::()]; + core.read_8(addr, &mut buffer)?; + + let mut rb: MaybeUninit = MaybeUninit::uninit(); + // SAFETY: Copying data from a HW defined struct into that struct. + unsafe { + let dest_ptr = rb.as_mut_ptr() as *mut u8; + std::ptr::copy_nonoverlapping(buffer.as_ptr(), dest_ptr, buffer.len()); + + Ok(rb.assume_init()) + } +} + +pub fn show_gpio_with_config( + context: &mut ExecutionContext, + gpio_input: &HiffyFunction, + args: &[(u16, Option, String)], + results: &[Result, IpcError>], +) -> Result<()> { + let mut config_cache = ConfigCache::new(); + for (ndx, arg) in args.iter().enumerate() { + match arg.1 { + Some(pin) => { + let config = + config_cache.get_pin_config(context, &arg.2, pin as u32)?; + println!( + "{}:{:<2} = {} {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + }, + config + ); + } + + None => match results[ndx] { + Err(code) => { + println!("Port {}: {}", arg.2, gpio_input.strerror(code)) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + + for i in 0..16 { + let config = config_cache + .get_pin_config(context, &arg.2, i as u32)?; + println!( + "{}:{:<2} = {} {}", + arg.2, + i, + (v >> i) & 1, + config + ); + } + } + }, + } + } + Ok(()) +} From a684daf65ea8346dffcd9761c4d73623d290284b Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Tue, 14 Oct 2025 13:49:48 -0700 Subject: [PATCH 3/7] Generalize to support STM32H743 and emit errors for unknown chips. --- cmd/gpio/src/lib.rs | 27 ++++--- cmd/gpio/src/{stm32h753.rs => stm32h7.rs} | 94 ++++++++++++++++------- 2 files changed, 85 insertions(+), 36 deletions(-) rename cmd/gpio/src/{stm32h753.rs => stm32h7.rs} (67%) diff --git a/cmd/gpio/src/lib.rs b/cmd/gpio/src/lib.rs index 165c5d4d..bc80b01d 100644 --- a/cmd/gpio/src/lib.rs +++ b/cmd/gpio/src/lib.rs @@ -105,7 +105,7 @@ //! ``` //! -mod stm32h753; +mod stm32h7; use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; @@ -122,14 +122,24 @@ fn show_gpio_with_config( args: &[(u16, Option, String)], results: &[Result, IpcError>], ) -> Result<()> { - let hubris = context.archive.as_ref().unwrap(); + let hubris = context + .archive + .as_ref() + .ok_or_else(|| anyhow!("Cannot get GPIO config: archive not loaded"))?; if let Some(chip) = hubris.chip() { - match chip.as_str() { - "stm32h7" => { - // Call the stm32h753-specific function - stm32h753::show_gpio_with_config(context, gpio_input, args, results) - } - _ => Err(anyhow!("GPIO `--with-config` is not supported for chip '{chip}'")), + if chip.to_ascii_lowercase().starts_with("stm32h7") { + // Call the stm32h7-specific function + stm32h7::show_gpio_with_config( + context, + gpio_input, + chip.as_str(), + args, + results, + ) + } else { + Err(anyhow!( + "GPIO `--with-config` is not supported for chip '{chip}'" + )) } } else { Err(anyhow!( @@ -138,7 +148,6 @@ fn show_gpio_with_config( } } - use std::convert::TryInto; #[derive(Parser, Debug)] diff --git a/cmd/gpio/src/stm32h753.rs b/cmd/gpio/src/stm32h7.rs similarity index 67% rename from cmd/gpio/src/stm32h753.rs rename to cmd/gpio/src/stm32h7.rs index cfb63733..f978f186 100644 --- a/cmd/gpio/src/stm32h753.rs +++ b/cmd/gpio/src/stm32h7.rs @@ -1,9 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::{anyhow, Result}; use humility_cli::ExecutionContext; use humility_hiffy::IpcError; -use std::mem::MaybeUninit; use std::fmt; -use anyhow::{anyhow, Result}; - +use std::mem::MaybeUninit; + use device::gpioa::RegisterBlock; use humility_hiffy::HiffyFunction; use std::collections::BTreeMap; @@ -38,27 +42,30 @@ impl fmt::Display for PinConfig { } } +/// Store an STM32H7 GPIO group ID ('A', 'B', etc.) and the associated GPIO +/// configuration register block to avoid repeated IO for each Pin. struct ConfigCache { - cache: BTreeMap, + cache: BTreeMap, } impl ConfigCache { fn new() -> ConfigCache { - let cache: BTreeMap = BTreeMap::new(); + let cache: BTreeMap = BTreeMap::new(); Self { cache } } fn get_pin_config( &mut self, context: &mut ExecutionContext, - group: &str, + chip: &str, + group: char, pin: u32, ) -> Result { - let rb = match self.cache.get(group) { + let rb = match self.cache.get(&group) { None => { - let rb = read_gpio_register_block(context, group)?; - self.cache.insert(group.to_string(), rb); - self.cache.get(group).unwrap() + let rb = read_gpio_register_block(context, chip, group)?; + self.cache.insert(group, rb); + self.cache.get(&group).unwrap() } Some(rb) => rb, }; @@ -143,30 +150,52 @@ impl ConfigCache { } } -fn stm32h753_gpio_registerblock_addr(group: &str) -> Option { +/// Lookup STM32H7 GPIO configuration register addresses. +fn stm32h753_gpio_registerblock_addr(group: char) -> Option { // Section 11.4: GPIO registers match group { - "A" => Some(device::GPIOA::ptr() as u32), - "B" => Some(device::GPIOB::ptr() as u32), - "C" => Some(device::GPIOC::ptr() as u32), - "D" => Some(device::GPIOD::ptr() as u32), - "E" => Some(device::GPIOE::ptr() as u32), - "F" => Some(device::GPIOF::ptr() as u32), - "G" => Some(device::GPIOG::ptr() as u32), - "H" => Some(device::GPIOH::ptr() as u32), - "I" => Some(device::GPIOI::ptr() as u32), - "J" => Some(device::GPIOJ::ptr() as u32), - "K" => Some(device::GPIOK::ptr() as u32), + 'A' => Some(device::GPIOA::ptr() as u32), + 'B' => Some(device::GPIOB::ptr() as u32), + 'C' => Some(device::GPIOC::ptr() as u32), + 'D' => Some(device::GPIOD::ptr() as u32), + 'E' => Some(device::GPIOE::ptr() as u32), + 'F' => Some(device::GPIOF::ptr() as u32), + 'G' => Some(device::GPIOG::ptr() as u32), + 'H' => Some(device::GPIOH::ptr() as u32), + 'I' => Some(device::GPIOI::ptr() as u32), + 'J' => Some(device::GPIOJ::ptr() as u32), + 'K' => Some(device::GPIOK::ptr() as u32), _ => None, } } +// Several other STM32 families share the same GPIO register format as the +// STM32H7 series: +// - STM32F7 series +// - STM32G4 series +// Support for those chips can be added if needed. +// Note: verify that the non-STM32H7 series are using the same addressing or adapt. +fn chip_has_group(chip: &str, group: char) -> Result { + let name = chip.to_ascii_lowercase(); + + if name.starts_with("stm32h753") { + Ok(matches!(group, 'A'..='K')) + } else if name.starts_with("stm32h743") { + Ok(matches!(group, 'A'..='I')) + } else { + Err(anyhow!("unknown STM32H7 chip '{}'", chip)) + } +} + fn read_gpio_register_block( context: &mut ExecutionContext, - group: &str, + chip: &str, + group: char, ) -> Result { let core = &mut **context.core.as_mut().unwrap(); - let addr = stm32h753_gpio_registerblock_addr(group).ok_or(anyhow!("no address for GPIO group {}", group))?; + chip_has_group(chip, group)?; + let addr = stm32h753_gpio_registerblock_addr(group) + .ok_or(anyhow!("no address for GPIO group {}", group))?; let mut buffer = vec![0u8; std::mem::size_of::()]; core.read_8(addr, &mut buffer)?; @@ -183,6 +212,7 @@ fn read_gpio_register_block( pub fn show_gpio_with_config( context: &mut ExecutionContext, gpio_input: &HiffyFunction, + chip: &str, args: &[(u16, Option, String)], results: &[Result, IpcError>], ) -> Result<()> { @@ -190,8 +220,13 @@ pub fn show_gpio_with_config( for (ndx, arg) in args.iter().enumerate() { match arg.1 { Some(pin) => { - let config = - config_cache.get_pin_config(context, &arg.2, pin as u32)?; + let group = arg + .2 + .chars() + .next() + .ok_or(anyhow!("invalid group '{}'", arg.2))?; + let config = config_cache + .get_pin_config(context, chip, group, pin as u32)?; println!( "{}:{:<2} = {} {}", arg.2, @@ -219,8 +254,13 @@ pub fn show_gpio_with_config( let v = u16::from_le_bytes(*arr); for i in 0..16 { + let group = arg + .2 + .chars() + .next() + .ok_or(anyhow!("invalid group '{}'", arg.2))?; let config = config_cache - .get_pin_config(context, &arg.2, i as u32)?; + .get_pin_config(context, chip, group, i as u32)?; println!( "{}:{:<2} = {} {}", arg.2, From 2a1411246b0dfc4d3eb0d8d1c8e6aa29e1ef6276 Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Tue, 14 Oct 2025 14:06:46 -0700 Subject: [PATCH 4/7] fix formatting complaint --- cmd/gpio/src/lib.rs | 2 +- cmd/gpio/src/stm32h7.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gpio/src/lib.rs b/cmd/gpio/src/lib.rs index bc80b01d..83d27219 100644 --- a/cmd/gpio/src/lib.rs +++ b/cmd/gpio/src/lib.rs @@ -111,7 +111,7 @@ use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; use humility_hiffy::HiffyContext; -use anyhow::{anyhow, bail, Result}; +use anyhow::{Result, anyhow, bail}; use clap::{CommandFactory, Parser}; use hif::*; use humility_hiffy::IpcError; diff --git a/cmd/gpio/src/stm32h7.rs b/cmd/gpio/src/stm32h7.rs index f978f186..b15a6d3a 100644 --- a/cmd/gpio/src/stm32h7.rs +++ b/cmd/gpio/src/stm32h7.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use humility_cli::ExecutionContext; use humility_hiffy::IpcError; use std::fmt; From 6e07987a9253e7fb3cd35495f126213fb2fa764a Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Tue, 14 Oct 2025 16:31:10 -0700 Subject: [PATCH 5/7] Define the one needed struct instead of involving an stm32 crate. The stm32h7 crate and the stm32-metapac crates are not practical given that they cause difficulties in compiling humility for multiple OS environments and they require a good bit of code to coerce into making generalized GPIO register decoding. Just define the one needed structure. --- Cargo.lock | 161 +++++----------------------------------- Cargo.toml | 4 +- cmd/gpio/Cargo.toml | 2 - cmd/gpio/src/stm32h7.rs | 99 +++++++++++++++--------- 4 files changed, 82 insertions(+), 184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5edfd858..693c6bbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,21 +204,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version 0.2.3", -] - -[[package]] -name = "bare-metal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" - [[package]] name = "base64" version = "0.13.1" @@ -397,7 +382,7 @@ checksum = "7714a157da7991e23d90686b9524b9e12e0407a108647f52e9328f4b3d51ac7f" dependencies = [ "cargo-platform", "semver 0.11.0", - "semver-parser 0.10.2", + "semver-parser", "serde", "serde_json", ] @@ -507,7 +492,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -611,38 +596,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "cortex-m" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" -dependencies = [ - "bare-metal 0.2.5", - "bitfield", - "embedded-hal", - "volatile-register", -] - -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - [[package]] name = "cpp_demangle" version = "0.4.4" @@ -906,7 +859,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -923,7 +876,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -970,8 +923,8 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", - "syn 2.0.15", + "rustc_version", + "syn 2.0.106", ] [[package]] @@ -997,16 +950,6 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - [[package]] name = "encode_unicode" version = "0.3.6" @@ -1234,7 +1177,7 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version 0.4.0", + "rustc_version", "serde", "spin", "stable_deref_trait", @@ -1699,9 +1642,7 @@ dependencies = [ "humility-cli", "humility-cmd", "humility-hiffy", - "lazy_static", "parse_int", - "stm32h7", "zerocopy", ] @@ -2985,21 +2926,6 @@ dependencies = [ "serde", ] -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "nb" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" - [[package]] name = "nix" version = "0.26.2" @@ -3056,7 +2982,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -3437,9 +3363,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -3647,15 +3573,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -3744,16 +3661,7 @@ checksum = "22fc4f90c27b57691bbaf11d8ecc7cfbfe98a4da6dbe60226115d322aa80c06e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser 0.7.0", + "syn 2.0.106", ] [[package]] @@ -3762,7 +3670,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser 0.10.2", + "semver-parser", "serde", ] @@ -3772,12 +3680,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "semver-parser" version = "0.10.2" @@ -3825,7 +3727,7 @@ checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -4026,18 +3928,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stm32h7" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0faa648e03579befdd7267ab5c669624729028001fcf3c973832f53e310a06" -dependencies = [ - "bare-metal 1.0.0", - "cortex-m", - "cortex-m-rt", - "vcell", -] - [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -4091,7 +3981,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -4119,9 +4009,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4216,7 +4106,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -4392,12 +4282,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - [[package]] name = "vcpkg" version = "0.2.15" @@ -4416,15 +4300,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "volatile-register" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" -dependencies = [ - "vcell", -] - [[package]] name = "vsc7448-info" version = "0.1.0" @@ -4878,7 +4753,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f39ef469..7cbdc65c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -247,7 +247,6 @@ serde-xml-rs = "0.5.1" sha2 = "0.10.1" splitty = "0.1.0" srec = "0.2" -stm32h7 = { version = "0.14", features = ["stm32h753"] } strum = "0.22" strum_macros = "0.22" syn = "1.0" @@ -258,7 +257,8 @@ thiserror = "1.0" toml = "0.5" trycmd = "0.13.2" winapi = "0.3.9" -zerocopy = "0.6.1" +zerocopy = "0.6.6" +zerocopy-derive = "0.6.6" zip = "0.6.4" [profile.release] diff --git a/cmd/gpio/Cargo.toml b/cmd/gpio/Cargo.toml index 9b749a42..f18336ad 100644 --- a/cmd/gpio/Cargo.toml +++ b/cmd/gpio/Cargo.toml @@ -9,8 +9,6 @@ hif.workspace = true clap.workspace = true anyhow.workspace = true parse_int.workspace = true -lazy_static.workspace = true -stm32h7.workspace = true zerocopy.workspace = true humility-cli.workspace = true diff --git a/cmd/gpio/src/stm32h7.rs b/cmd/gpio/src/stm32h7.rs index b15a6d3a..b54b19f0 100644 --- a/cmd/gpio/src/stm32h7.rs +++ b/cmd/gpio/src/stm32h7.rs @@ -6,12 +6,43 @@ use anyhow::{Result, anyhow}; use humility_cli::ExecutionContext; use humility_hiffy::IpcError; use std::fmt; -use std::mem::MaybeUninit; +use zerocopy::{AsBytes, FromBytes}; -use device::gpioa::RegisterBlock; use humility_hiffy::HiffyFunction; use std::collections::BTreeMap; -use stm32h7::stm32h753 as device; + +/// STM32H7 series GPIO Registers +/// +/// Pulling these in from any of the available public crates is +/// problematic. The "stm32h7" crate will prevent us from linking +/// on Windows and the "stm32-metapac" crate forces us to calculate +/// offsets for all of the fields in the GPIO Register blocks. +/// In this case, the code is more readable and easier to maintain +/// if we just get our information from the ST Micro datasheet: "RM0433". +#[derive(FromBytes, AsBytes, Copy, Clone)] +#[repr(C)] +struct GpioRegisters { + /// Mode Register + moder: u32, // 0x00 + /// Output Type Register + otyper: u32, // 0x04 + /// Output Speed Register + ospeedr: u32, // 0x08 + /// Pull-Up/Pull-Down Register + pupdr: u32, // 0x0C + /// Input Data Register + idr: u32, // 0x10 + /// Output Data Register + odr: u32, // 0x14 + /// Bit Set/Reset Register (write-only) + bsrr: u32, // 0x18 + /// Configuration Lock Register + lckr: u32, // 0x1C + /// Alternate Function Low Register + afrl: u32, // 0x20 + /// Alternate Function High Register + afrh: u32, // 0x24 +} struct PinConfig { mode: String, @@ -45,12 +76,12 @@ impl fmt::Display for PinConfig { /// Store an STM32H7 GPIO group ID ('A', 'B', etc.) and the associated GPIO /// configuration register block to avoid repeated IO for each Pin. struct ConfigCache { - cache: BTreeMap, + cache: BTreeMap, } impl ConfigCache { fn new() -> ConfigCache { - let cache: BTreeMap = BTreeMap::new(); + let cache: BTreeMap = BTreeMap::new(); Self { cache } } @@ -70,7 +101,7 @@ impl ConfigCache { Some(rb) => rb, }; - let reg = rb.moder.read().bits(); + let reg = rb.moder; let mode = match (reg >> (pin * 2)) & 3 { 0 => "Input", 1 => "Output", @@ -79,14 +110,14 @@ impl ConfigCache { _ => unreachable!(), }; - let reg = rb.otyper.read().bits(); + let reg = rb.otyper; let otype = match (reg >> pin) & 1 { 0 => "OutPushPull", 1 => "OutOpenDrain", _ => unreachable!(), }; - let reg = rb.ospeedr.read().bits(); + let reg = rb.ospeedr; let speed = match (reg >> (pin * 2)) & 3 { 0 => "LowSpeed", 1 => "MediumSpeed", @@ -95,7 +126,7 @@ impl ConfigCache { _ => unreachable!(), }; - let reg = rb.pupdr.read().bits(); + let reg = rb.pupdr; let pull = match (reg >> (pin * 2)) & 3 { 0 => "NoPull", 1 => "PullUp", @@ -104,14 +135,14 @@ impl ConfigCache { _ => unreachable!(), }; - let reg = rb.idr.read().bits(); + let reg = rb.idr; let input = match (reg >> pin) & 1 { 0 => "InZero", 1 => "InOne", _ => unreachable!(), }; - let reg = rb.odr.read().bits(); + let reg = rb.odr; let output = match (reg >> pin) & 1 { 0 => "OutZero", 1 => "OutOne", @@ -119,7 +150,7 @@ impl ConfigCache { }; // Note: Bit 16 indicates that the register is locked or unlocked. - let reg = rb.lckr.read().bits(); + let reg = rb.lckr; let lock = match (reg >> pin) & 1 { 0 => "Unlocked", 1 => "Locked", @@ -129,10 +160,10 @@ impl ConfigCache { let alternate = format!( "AF{}", if pin < 8 { - let reg = rb.afrl.read().bits(); + let reg = rb.afrl; (reg >> (pin * 4)) & 15 } else { - let reg = rb.afrh.read().bits(); + let reg = rb.afrh; (reg >> ((pin - 8) * 4)) & 15 } ); @@ -154,17 +185,17 @@ impl ConfigCache { fn stm32h753_gpio_registerblock_addr(group: char) -> Option { // Section 11.4: GPIO registers match group { - 'A' => Some(device::GPIOA::ptr() as u32), - 'B' => Some(device::GPIOB::ptr() as u32), - 'C' => Some(device::GPIOC::ptr() as u32), - 'D' => Some(device::GPIOD::ptr() as u32), - 'E' => Some(device::GPIOE::ptr() as u32), - 'F' => Some(device::GPIOF::ptr() as u32), - 'G' => Some(device::GPIOG::ptr() as u32), - 'H' => Some(device::GPIOH::ptr() as u32), - 'I' => Some(device::GPIOI::ptr() as u32), - 'J' => Some(device::GPIOJ::ptr() as u32), - 'K' => Some(device::GPIOK::ptr() as u32), + 'A' => Some(0x5802_0000u32), + 'B' => Some(0x5802_0400u32), + 'C' => Some(0x5802_0800u32), + 'D' => Some(0x5802_0C00u32), + 'E' => Some(0x5802_1000u32), + 'F' => Some(0x5802_1400u32), + 'G' => Some(0x5802_1800u32), + 'H' => Some(0x5802_1C00u32), + 'I' => Some(0x5802_2000u32), + 'J' => Some(0x5802_2400u32), + 'K' => Some(0x5802_2800u32), _ => None, } } @@ -191,22 +222,16 @@ fn read_gpio_register_block( context: &mut ExecutionContext, chip: &str, group: char, -) -> Result { - let core = &mut **context.core.as_mut().unwrap(); +) -> Result { + let core = context.core.as_mut().unwrap(); chip_has_group(chip, group)?; let addr = stm32h753_gpio_registerblock_addr(group) .ok_or(anyhow!("no address for GPIO group {}", group))?; - let mut buffer = vec![0u8; std::mem::size_of::()]; + let mut buffer = vec![0u8; std::mem::size_of::()]; core.read_8(addr, &mut buffer)?; - - let mut rb: MaybeUninit = MaybeUninit::uninit(); - // SAFETY: Copying data from a HW defined struct into that struct. - unsafe { - let dest_ptr = rb.as_mut_ptr() as *mut u8; - std::ptr::copy_nonoverlapping(buffer.as_ptr(), dest_ptr, buffer.len()); - - Ok(rb.assume_init()) - } + let rb = GpioRegisters::read_from_prefix(buffer.as_slice()) + .ok_or(anyhow!("Slice length is incorrect for GpioRegisters"))?; + Ok(rb) } pub fn show_gpio_with_config( From c3533ce13768b3e891106ac10ffb4e674810e9e2 Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Wed, 15 Oct 2025 22:09:20 -0700 Subject: [PATCH 6/7] GPIO configuration display is adapted to the actual chip being used. - Updated documentation to reflect GPIO configuration output specific to chip configuration. - Non-existant pins are not printed. - Added tests for the new behavior. --- cmd/gpio/src/config_cache.rs | 63 +++ cmd/gpio/src/lib.rs | 159 ++++++- cmd/gpio/src/stm32h7.rs | 391 ++++++---------- cmd/gpio/src/stm32h7_parts.rs | 771 +++++++++++++++++++++++++++++++ cmd/gpio/src/stm32h7_testdata.rs | 394 ++++++++++++++++ 5 files changed, 1494 insertions(+), 284 deletions(-) create mode 100644 cmd/gpio/src/config_cache.rs create mode 100644 cmd/gpio/src/stm32h7_parts.rs create mode 100644 cmd/gpio/src/stm32h7_testdata.rs diff --git a/cmd/gpio/src/config_cache.rs b/cmd/gpio/src/config_cache.rs new file mode 100644 index 00000000..bad8314d --- /dev/null +++ b/cmd/gpio/src/config_cache.rs @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Generic cache for GPIO configuration register blocks. +//! +//! This module provides a chip-independent way to cache raw GPIO register +//! block data from device memory. The cache stores raw bytes indexed by +//! GPIO group identifiers, avoiding repeated memory reads for the same +//! GPIO port. + +use anyhow::Result; +use humility_cli::ExecutionContext; +use std::collections::BTreeMap; + +/// A generic cache for GPIO configuration register blocks. +/// +/// This cache stores raw byte data for GPIO register blocks, indexed by +/// a group identifier (typically a character like 'A', 'B', etc.). The +/// cache is chip-independent and works with any architecture that organizes +/// GPIO pins into groups/ports with contiguous register blocks. +pub struct ConfigCache { + cache: BTreeMap>, +} + +impl ConfigCache { + /// Creates a new empty configuration cache. + pub fn new() -> ConfigCache { + ConfigCache { cache: BTreeMap::new() } + } + + /// Gets or fetches the raw register block data for a GPIO group. + /// + /// If the data for the specified group is already cached, returns it + /// directly. Otherwise, calls the provided `fetch_fn` to read the data + /// from device memory, caches it, and returns it. + /// + /// # Arguments + /// + /// * `context` - Execution context with access to the device core + /// * `group` - GPIO group identifier (e.g., 'A', 'B', 'C') + /// * `fetch_fn` - Function that fetches the register block from device memory + /// + /// # Returns + /// + /// A reference to the cached raw register block data + pub fn get_or_fetch( + &mut self, + context: &mut ExecutionContext, + group: char, + fetch_fn: F, + ) -> Result<&[u8]> + where + F: FnOnce(&mut ExecutionContext, char) -> Result>, + { + use std::collections::btree_map::Entry; + if let Entry::Vacant(e) = self.cache.entry(group) { + let data = fetch_fn(context, group)?; + e.insert(data); + } + Ok(self.cache.get(&group).unwrap().as_slice()) + } +} diff --git a/cmd/gpio/src/lib.rs b/cmd/gpio/src/lib.rs index 83d27219..2171c701 100644 --- a/cmd/gpio/src/lib.rs +++ b/cmd/gpio/src/lib.rs @@ -59,6 +59,7 @@ //! ```console //! $ humility gpio --input //! humility: attached via ST-Link V3 +//! Chip: STM32H753ZITx //! Pin 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //! ----------------------------------------------------------------------- //! Port A 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 1 @@ -68,24 +69,52 @@ //! Port E 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! Port F 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! Port G 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port H 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port I 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +//! Port H 0 0 . . . . . . . . . . . . . . //! ``` -//! To get input values with STM32H753 GPIO configuration settings, -//! use the `--with-config` or `-w` flag with --input: +//! +//! Note that the chip name is displayed, and GPIO groups are filtered based on +//! the specific chip variant. For partially populated GPIO groups (like Port H +//! above with only 2 pins), non-existent pins are shown as `.` +//! +//! If a chip name is not in the internal database of chip names, then the +//! old behavior of this program is used; no GPIO config info will be shown +//! and it will be assumed that the part has 176 GPIO pins in groups A to K. +//! +//! To print input values with STM32H7 series GPIO configuration settings, +//! use the `--with-config` or `-w` flag with `--input`: //! //! ```console -//! $ humility gpio --input -with-config --pins B:0,B:14,E:1 +//! $ humility gpio --input --with-config --pins B:0,B:14,E:1 //! humility: attached to 0483:3754:002F00174741500820383733 via ST-Link V3 +//! Chip: STM32H753ZITx //! B:0 = 0 Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero //! B:14 = 1 Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero //! E:1 = 0 Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero //! ``` //! -//! If no pins are specified, then the pin names, values and config settings -//! are printed one per line for all GPIO pins. +//! If you query pins that don't exist on the chip variant, they will show as `None`: +//! +//! ```console +//! $ humility gpio --input --pins J:4,K:9 +//! humility: attached via ST-Link V3 +//! Chip: STM32H753ZITx +//! J:4 = None +//! K:9 = None +//! ``` +//! +//! With `--with-config`, non-existent pins or groups show `None` for configuration: +//! +//! ```console +//! $ humility gpio --input --with-config --pins H:5,J:4 +//! humility: attached via ST-Link V3 +//! Chip: STM32H753ZITx +//! H:5 = 0 None +//! J:4 = 0 None +//! ``` +//! +//! If no pins are specified with `--with-config`, then the pin names, values +//! and config settings are printed one per line for all available GPIO pins on +//! the chip variant. //! //! ### Configure //! @@ -105,7 +134,11 @@ //! ``` //! +mod config_cache; mod stm32h7; +mod stm32h7_parts; +#[cfg(test)] +mod stm32h7_testdata; use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; @@ -310,33 +343,98 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> { let results = hiffy_context.run(core, ops.as_slice(), None)?; + // Get chip info for filtering and display + let chip_name = hubris.chip(); + let (max_group, chip_part) = if let Some(chip) = &chip_name { + if chip.to_ascii_lowercase().starts_with("stm32h7") { + use crate::stm32h7_parts::Stm32H7Series; + let part = Stm32H7Series::find_part(chip, true); + + // Warn about unknown chips + if !part.is_known() { + eprintln!( + "Warning: Unknown STM32H7 chip '{}'. Using default GPIO layout (groups A-K).", + chip + ); + eprintln!( + " Configuration display (--with-config) is not available for unknown chips." + ); + } + + (Some(part.max_group()), Some(part)) + } else { + (None, None) + } + } else { + (None, None) + }; + if subargs.input { let mut header = false; if subargs.with_config { + // Print chip name + if let Some(chip) = &chip_name { + println!("Chip: {}", chip); + } show_gpio_with_config(context, &gpio_input, &args, &results)?; } else { + // Print chip name for grid display too + if let Some(chip) = &chip_name { + println!("Chip: {}", chip); + } + for (ndx, arg) in args.iter().enumerate() { + // Check if this group exists on the chip + let group = arg.2.chars().next(); + let group_exists = + if let (Some(g), Some(max_g)) = (group, max_group) { + g >= 'A' && g <= max_g + } else { + true // If we can't determine, assume it exists + }; + match arg.1 { Some(pin) => { - println!( - "{}:{:<2} = {}", - arg.2, - pin, - match results[ndx] { - Err(code) => { - gpio_input.strerror(code) - } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - format!("{}", (v >> pin) & 1) + // Check if pin exists (group exists and pin within range) + let pin_exists = + if let (Some(g), Some(part)) = (group, chip_part) { + g >= 'A' + && g <= part.max_group() + && pin < part.pins_in_group(g) + } else { + group_exists // Fall back to group-level check + }; + + if !pin_exists { + // Pin doesn't exist on this chip + println!("{}:{:<2} = None", arg.2, pin); + } else { + println!( + "{}:{:<2} = {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = + val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } } - } - ); + ); + } } None => { + // Skip ports that don't exist on this chip variant + if !group_exists { + continue; + } + if !header { print!("{:7}", "Pin"); @@ -365,8 +463,21 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> { let arr: &[u8; 2] = val[0..2].try_into()?; let v = u16::from_le_bytes(*arr); + // Determine how many pins exist in this group + let max_pin = if let (Some(g), Some(part)) = + (group, chip_part) + { + part.pins_in_group(g) + } else { + 16 // Default to all 16 if we can't determine + }; + for i in 0..16 { - print!("{:4}", (v >> i) & 1) + if i < max_pin { + print!("{:4}", (v >> i) & 1) + } else { + print!(" .") + } } println!(); } diff --git a/cmd/gpio/src/stm32h7.rs b/cmd/gpio/src/stm32h7.rs index b54b19f0..0f1f8e09 100644 --- a/cmd/gpio/src/stm32h7.rs +++ b/cmd/gpio/src/stm32h7.rs @@ -4,234 +4,25 @@ use anyhow::{Result, anyhow}; use humility_cli::ExecutionContext; -use humility_hiffy::IpcError; -use std::fmt; -use zerocopy::{AsBytes, FromBytes}; - use humility_hiffy::HiffyFunction; -use std::collections::BTreeMap; - -/// STM32H7 series GPIO Registers -/// -/// Pulling these in from any of the available public crates is -/// problematic. The "stm32h7" crate will prevent us from linking -/// on Windows and the "stm32-metapac" crate forces us to calculate -/// offsets for all of the fields in the GPIO Register blocks. -/// In this case, the code is more readable and easier to maintain -/// if we just get our information from the ST Micro datasheet: "RM0433". -#[derive(FromBytes, AsBytes, Copy, Clone)] -#[repr(C)] -struct GpioRegisters { - /// Mode Register - moder: u32, // 0x00 - /// Output Type Register - otyper: u32, // 0x04 - /// Output Speed Register - ospeedr: u32, // 0x08 - /// Pull-Up/Pull-Down Register - pupdr: u32, // 0x0C - /// Input Data Register - idr: u32, // 0x10 - /// Output Data Register - odr: u32, // 0x14 - /// Bit Set/Reset Register (write-only) - bsrr: u32, // 0x18 - /// Configuration Lock Register - lckr: u32, // 0x1C - /// Alternate Function Low Register - afrl: u32, // 0x20 - /// Alternate Function High Register - afrh: u32, // 0x24 -} - -struct PinConfig { - mode: String, - otype: String, - speed: String, - pull: String, - input: String, - output: String, - lock: String, - alternate: String, -} - -impl fmt::Display for PinConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Use the write! macro to format the output to the formatter 'f' - write!( - f, - "{}:{}:{}:{}:{}:{}:{}:{}", - self.mode, - self.otype, - self.speed, - self.pull, - self.alternate, - self.lock, - self.input, - self.output - ) - } -} - -/// Store an STM32H7 GPIO group ID ('A', 'B', etc.) and the associated GPIO -/// configuration register block to avoid repeated IO for each Pin. -struct ConfigCache { - cache: BTreeMap, -} - -impl ConfigCache { - fn new() -> ConfigCache { - let cache: BTreeMap = BTreeMap::new(); - Self { cache } - } - - fn get_pin_config( - &mut self, - context: &mut ExecutionContext, - chip: &str, - group: char, - pin: u32, - ) -> Result { - let rb = match self.cache.get(&group) { - None => { - let rb = read_gpio_register_block(context, chip, group)?; - self.cache.insert(group, rb); - self.cache.get(&group).unwrap() - } - Some(rb) => rb, - }; - - let reg = rb.moder; - let mode = match (reg >> (pin * 2)) & 3 { - 0 => "Input", - 1 => "Output", - 2 => "Alternate", - 3 => "Analog", - _ => unreachable!(), - }; - - let reg = rb.otyper; - let otype = match (reg >> pin) & 1 { - 0 => "OutPushPull", - 1 => "OutOpenDrain", - _ => unreachable!(), - }; - - let reg = rb.ospeedr; - let speed = match (reg >> (pin * 2)) & 3 { - 0 => "LowSpeed", - 1 => "MediumSpeed", - 2 => "HighSpeed", - 3 => "VeryHighSpeed", - _ => unreachable!(), - }; - - let reg = rb.pupdr; - let pull = match (reg >> (pin * 2)) & 3 { - 0 => "NoPull", - 1 => "PullUp", - 2 => "PullDown", - 3 => "ReservedPull", - _ => unreachable!(), - }; - - let reg = rb.idr; - let input = match (reg >> pin) & 1 { - 0 => "InZero", - 1 => "InOne", - _ => unreachable!(), - }; - - let reg = rb.odr; - let output = match (reg >> pin) & 1 { - 0 => "OutZero", - 1 => "OutOne", - _ => unreachable!(), - }; - - // Note: Bit 16 indicates that the register is locked or unlocked. - let reg = rb.lckr; - let lock = match (reg >> pin) & 1 { - 0 => "Unlocked", - 1 => "Locked", - _ => unreachable!(), - }; - - let alternate = format!( - "AF{}", - if pin < 8 { - let reg = rb.afrl; - (reg >> (pin * 4)) & 15 - } else { - let reg = rb.afrh; - (reg >> ((pin - 8) * 4)) & 15 - } - ); - - Ok(PinConfig { - mode: mode.to_string(), - otype: otype.to_string(), - speed: speed.to_string(), - pull: pull.to_string(), - input: input.to_string(), - output: output.to_string(), - lock: lock.to_string(), - alternate, - }) - } -} - -/// Lookup STM32H7 GPIO configuration register addresses. -fn stm32h753_gpio_registerblock_addr(group: char) -> Option { - // Section 11.4: GPIO registers - match group { - 'A' => Some(0x5802_0000u32), - 'B' => Some(0x5802_0400u32), - 'C' => Some(0x5802_0800u32), - 'D' => Some(0x5802_0C00u32), - 'E' => Some(0x5802_1000u32), - 'F' => Some(0x5802_1400u32), - 'G' => Some(0x5802_1800u32), - 'H' => Some(0x5802_1C00u32), - 'I' => Some(0x5802_2000u32), - 'J' => Some(0x5802_2400u32), - 'K' => Some(0x5802_2800u32), - _ => None, - } -} - -// Several other STM32 families share the same GPIO register format as the -// STM32H7 series: -// - STM32F7 series -// - STM32G4 series -// Support for those chips can be added if needed. -// Note: verify that the non-STM32H7 series are using the same addressing or adapt. -fn chip_has_group(chip: &str, group: char) -> Result { - let name = chip.to_ascii_lowercase(); +use humility_hiffy::IpcError; - if name.starts_with("stm32h753") { - Ok(matches!(group, 'A'..='K')) - } else if name.starts_with("stm32h743") { - Ok(matches!(group, 'A'..='I')) - } else { - Err(anyhow!("unknown STM32H7 chip '{}'", chip)) - } -} +use crate::config_cache::ConfigCache; +use crate::stm32h7_parts::{Stm32H7Part, Stm32H7Series}; +/// Read GPIO register block from device memory fn read_gpio_register_block( context: &mut ExecutionContext, - chip: &str, + part: &Stm32H7Part, group: char, -) -> Result { +) -> Result> { let core = context.core.as_mut().unwrap(); - chip_has_group(chip, group)?; - let addr = stm32h753_gpio_registerblock_addr(group) + let addr = part + .gpio_register_block_address(group) .ok_or(anyhow!("no address for GPIO group {}", group))?; - let mut buffer = vec![0u8; std::mem::size_of::()]; + let mut buffer = vec![0u8; Stm32H7Part::gpio_register_block_size()]; core.read_8(addr, &mut buffer)?; - let rb = GpioRegisters::read_from_prefix(buffer.as_slice()) - .ok_or(anyhow!("Slice length is incorrect for GpioRegisters"))?; - Ok(rb) + Ok(buffer) } pub fn show_gpio_with_config( @@ -241,61 +32,141 @@ pub fn show_gpio_with_config( args: &[(u16, Option, String)], results: &[Result, IpcError>], ) -> Result<()> { + // Look up chip in database + let part = Stm32H7Series::find_part(chip, true); + + // Reject --with-config for unknown chips (backward compatibility: only basic GPIO ops) + if !part.is_known() { + return Err(anyhow!( + "GPIO `--with-config` is not supported for unknown chip '{}'. \ + Use `--input` without `-w` for basic GPIO operations.", + chip + )); + } + let mut config_cache = ConfigCache::new(); + for (ndx, arg) in args.iter().enumerate() { + let group = + arg.2.chars().next().ok_or(anyhow!("invalid group '{}'", arg.2))?; + + // Check if this group exists on this chip variant + let group_available = group >= 'A' && group <= part.max_group(); + match arg.1 { Some(pin) => { - let group = arg - .2 - .chars() - .next() - .ok_or(anyhow!("invalid group '{}'", arg.2))?; - let config = config_cache - .get_pin_config(context, chip, group, pin as u32)?; - println!( - "{}:{:<2} = {} {}", - arg.2, - pin, - match results[ndx] { - Err(code) => { - gpio_input.strerror(code) + // Specific pin requested + if !group_available { + // Group doesn't exist on this chip - show value but no config + println!( + "{}:{:<2} = {} None", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - format!("{}", (v >> pin) & 1) + ); + } else if pin >= part.pins_in_group(group) { + // Pin doesn't exist in this partially filled group + println!( + "{}:{:<2} = {} None", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } } - }, - config - ); + ); + } else { + // Group exists and pin is valid - show config + let register_data = config_cache.get_or_fetch( + context, + group, + |ctx, g| read_gpio_register_block(ctx, part, g), + )?; + + let config = + Stm32H7Part::get_pin_config(register_data, pin)?; + + println!( + "{}:{:<2} = {} {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + }, + config + ); + } } - None => match results[ndx] { - Err(code) => { - println!("Port {}: {}", arg.2, gpio_input.strerror(code)) + None => { + // All pins in port requested + if !group_available { + // Skip ports that don't exist on this chip variant + continue; } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - for i in 0..16 { - let group = arg - .2 - .chars() - .next() - .ok_or(anyhow!("invalid group '{}'", arg.2))?; - let config = config_cache - .get_pin_config(context, chip, group, i as u32)?; + match results[ndx] { + Err(code) => { println!( - "{}:{:<2} = {} {}", + "Port {}: {}", arg.2, - i, - (v >> i) & 1, - config - ); + gpio_input.strerror(code) + ) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + + // Get cached register block data + let register_data = config_cache.get_or_fetch( + context, + group, + |ctx, g| read_gpio_register_block(ctx, part, g), + )?; + + let max_pin = part.pins_in_group(group); + for i in 0..16 { + if i >= max_pin { + // Pin doesn't exist in this partially filled group - skip it + continue; + } + + let config = + Stm32H7Part::get_pin_config(register_data, i)?; + + println!( + "{}:{:<2} = {} {}", + arg.2, + i, + (v >> i) & 1, + config + ); + } } } - }, + } } } Ok(()) diff --git a/cmd/gpio/src/stm32h7_parts.rs b/cmd/gpio/src/stm32h7_parts.rs new file mode 100644 index 00000000..b393f550 --- /dev/null +++ b/cmd/gpio/src/stm32h7_parts.rs @@ -0,0 +1,771 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! STM32H7 parts database and GPIO configuration interpretation. +//! +//! This module is based on the STMCUFinder database and provides part lookup +//! and GPIO register interpretation for STM32H7 series microcontrollers. + +use anyhow::{Result, anyhow}; +use std::fmt; +use zerocopy::{AsBytes, FromBytes}; + +/// Represents a specific STM32H7 series microcontroller part. +#[derive(Debug, PartialEq, Clone)] +pub struct Stm32H7Part { + com_part_no: &'static str, + part_no: &'static str, + ref_no: &'static str, + gpio_count: u8, + groups: u8, + max_group: char, +} + +impl Stm32H7Part { + // --- Public Getters --- + #[cfg(test)] + pub fn com_part_no(&self) -> &'static str { + self.com_part_no + } + #[cfg(test)] + pub fn part_no(&self) -> &'static str { + self.part_no + } + #[cfg(test)] + pub fn ref_no(&self) -> &'static str { + self.ref_no + } + #[cfg(test)] + pub fn gpio_count(&self) -> u8 { + self.gpio_count + } + + #[cfg(test)] + pub fn groups(&self) -> u8 { + self.groups + } + pub fn max_group(&self) -> char { + self.max_group + } + + /// Returns true if this is a known chip from the database, false if it's + /// the fallback for backward compatibility with unknown chips. + pub fn is_known(&self) -> bool { + self.part_no != "Unknown" + } + + /// Get the number of valid pins in a specific GPIO group. + /// Returns number of pins (0-16) that actually exist in the group. + pub fn pins_in_group(&self, group: char) -> u8 { + if group < 'A' || group > self.max_group { + return 0; + } + + let group_index = (group as u8) - b'A'; + let full_groups = self.gpio_count / 16; + + if group_index < full_groups { + // This is a fully populated group + 16 + } else if group_index == full_groups { + // This is a partially populated group + let remainder = self.gpio_count % 16; + if remainder == 0 { 16 } else { remainder } + } else { + // Beyond the last group + 0 + } + } + + /// Lookup STM32H7 GPIO configuration register addresses. + /// + /// Calculates the register block address for a given GPIO group. + /// Each group is offset by 0x400 bytes from the base address. + /// This method validates against the chip's maximum supported group. + pub fn gpio_register_block_address(&self, group: char) -> Option { + // Section 11.4: GPIO registers + // Base address for GPIO Port A + const BASE_ADDR: u32 = 0x5802_0000; + const GROUP_OFFSET: u32 = 0x400; + + if group < 'A' || group > self.max_group { + return None; + } + + let group_index = (group as u8 - b'A') as u32; + Some(BASE_ADDR + (group_index * GROUP_OFFSET)) + } + + pub fn gpio_register_block_size() -> usize { + core::mem::size_of::() + } + + /// Get the detailed configuration of a specific pin. + pub fn get_pin_config( + register_block_data: &[u8], + pin: u8, + ) -> Result { + let rb = GpioRegisters::read_from_prefix(register_block_data).ok_or( + anyhow!("cannot convert register_block_data to GpioRegisters"), + )?; + // Note: Apart from Reserved values, all data can be mapped to valid `GpioRegisters`. + + let reg = rb.moder; + let mode = match (reg >> (pin * 2)) & 3 { + 0 => "Input", + 1 => "Output", + 2 => "Alternate", + 3 => "Analog", + _ => unreachable!(), + }; + + let reg = rb.otyper; + let otype = match (reg >> pin) & 1 { + 0 => "OutPushPull", + 1 => "OutOpenDrain", + _ => unreachable!(), + }; + + let reg = rb.ospeedr; + let speed = match (reg >> (pin * 2)) & 3 { + 0 => "LowSpeed", + 1 => "MediumSpeed", + 2 => "HighSpeed", + 3 => "VeryHighSpeed", + _ => unreachable!(), + }; + + let reg = rb.pupdr; + let pull = match (reg >> (pin * 2)) & 3 { + 0 => "NoPull", + 1 => "PullUp", + 2 => "PullDown", + 3 => "ReservedPull", + _ => unreachable!(), + }; + + let reg = rb.idr; + let input = match (reg >> pin) & 1 { + 0 => "InZero", + 1 => "InOne", + _ => unreachable!(), + }; + + let reg = rb.odr; + let output = match (reg >> pin) & 1 { + 0 => "OutZero", + 1 => "OutOne", + _ => unreachable!(), + }; + + // Note: Bit 16 indicates that the register is locked or unlocked. + let reg = rb.lckr; + let lock = match (reg >> pin) & 1 { + 0 => "Unlocked", + 1 => "Locked", + _ => unreachable!(), + }; + + let alternate = format!( + "AF{}", + if pin < 8 { + let reg = rb.afrl; + (reg >> (pin * 4)) & 15 + } else { + let reg = rb.afrh; + (reg >> ((pin - 8) * 4)) & 15 + } + ); + + Ok(PinConfig { + mode: mode.to_string(), + otype: otype.to_string(), + speed: speed.to_string(), + pull: pull.to_string(), + input: input.to_string(), + output: output.to_string(), + lock: lock.to_string(), + alternate, + }) + } +} + +/// Fallback part definition for unknown chips. +/// Provides backward compatibility with old behavior that assumed all chips +/// have GPIO groups A-K (11 groups, 176 pins total). +/// This allows basic GPIO operations without full chip knowledge. +const UNKNOWN_PART: Stm32H7Part = Stm32H7Part { + com_part_no: "Unknown", + part_no: "Unknown", + ref_no: "Unknown", + gpio_count: 176, // 11 groups * 16 pins + groups: 11, + max_group: 'K', +}; + +/// The main, read-only database of STM32H7 parts for normal (non-test) builds. +const PARTS: &[Stm32H7Part] = &[ + // Commercial Part No,Part No,Reference,I/O,Groups,MaxGroup + Stm32H7Part { + com_part_no: "STM32H753ZIT6", + part_no: "STM32H753ZI", + ref_no: "STM32H753ZITx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H753XIH6TR", + part_no: "STM32H753XI", + ref_no: "STM32H753XIHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H753XIH6", + part_no: "STM32H753XI", + ref_no: "STM32H753XIHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H753VIT6", + part_no: "STM32H753VI", + ref_no: "STM32H753VITx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H753VIH6", + part_no: "STM32H753VI", + ref_no: "STM32H753VIHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H753IIT6", + part_no: "STM32H753II", + ref_no: "STM32H753IITx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H753IIK6", + part_no: "STM32H753II", + ref_no: "STM32H753IIKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H753BIT6", + part_no: "STM32H753BI", + ref_no: "STM32H753BITx", + gpio_count: 168, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H753AII6", + part_no: "STM32H753AI", + ref_no: "STM32H753AIIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743ZIT6TR", + part_no: "STM32H743ZI", + ref_no: "STM32H743ZITx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H743ZIT6", + part_no: "STM32H743ZI", + ref_no: "STM32H743ZITx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H743ZGT6", + part_no: "STM32H743ZG", + ref_no: "STM32H743ZGTx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H743XIH6", + part_no: "STM32H743XI", + ref_no: "STM32H743XIHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743XGH6", + part_no: "STM32H743XG", + ref_no: "STM32H743XGHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743VIT6TR", + part_no: "STM32H743VI", + ref_no: "STM32H743VITx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VIT6", + part_no: "STM32H743VI", + ref_no: "STM32H743VITx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VIH6TR", + part_no: "STM32H743VI", + ref_no: "STM32H743VIHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VIH6", + part_no: "STM32H743VI", + ref_no: "STM32H743VIHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VGT6", + part_no: "STM32H743VG", + ref_no: "STM32H743VGTx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VGH6", + part_no: "STM32H743VG", + ref_no: "STM32H743VGHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743IIT6", + part_no: "STM32H743II", + ref_no: "STM32H743IITx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IIK6TR", + part_no: "STM32H743II", + ref_no: "STM32H743IIKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IIK6", + part_no: "STM32H743II", + ref_no: "STM32H743IIKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IGT6", + part_no: "STM32H743IG", + ref_no: "STM32H743IGTx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IGK6", + part_no: "STM32H743IG", + ref_no: "STM32H743IGKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743BIT6", + part_no: "STM32H743BI", + ref_no: "STM32H743BITx", + gpio_count: 168, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743BGT6", + part_no: "STM32H743BG", + ref_no: "STM32H743BGTx", + gpio_count: 168, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743AII6TR", + part_no: "STM32H743AI", + ref_no: "STM32H743AIIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743AII6", + part_no: "STM32H743AI", + ref_no: "STM32H743AIIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743AGI6", + part_no: "STM32H743AG", + ref_no: "STM32H743AGIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, +]; + +/// A static, read-only database of parts that only exist for testing purposes. +/// This is conditionally compiled and is only available when running `cargo test`. +#[cfg(test)] +const TEST_ONLY_PARTS: &[Stm32H7Part] = &[ + // Entry to create an ambiguous match on `part_no` for testing. + Stm32H7Part { + com_part_no: "STM32H753BI-TEST", + part_no: "STM32H753BI", + ref_no: "STM32H753BITx-TEST", + gpio_count: 50, + groups: 4, + max_group: 'D', + }, +]; + +/// STM32H7 series GPIO Registers +/// +/// Pulling these in from any of the available public crates is +/// problematic. The "stm32h7" crate will prevent us from linking +/// on Windows and the "stm32-metapac" crate forces us to calculate +/// offsets for all of the fields in the GPIO Register blocks. +/// In this case, the code is more readable and easier to maintain +/// if we just get our information from the ST Micro datasheet: "RM0433". +#[derive(FromBytes, AsBytes, Copy, Clone)] +#[repr(C)] +pub struct GpioRegisters { + /// Mode Register + pub moder: u32, // 0x00 + /// Output Type Register + pub otyper: u32, // 0x04 + /// Output Speed Register + pub ospeedr: u32, // 0x08 + /// Pull-Up/Pull-Down Register + pub pupdr: u32, // 0x0C + /// Input Data Register + pub idr: u32, // 0x10 + /// Output Data Register + pub odr: u32, // 0x14 + /// Bit Set/Reset Register (write-only) + pub bsrr: u32, // 0x18 + /// Configuration Lock Register + pub lckr: u32, // 0x1C + /// Alternate Function Low Register + pub afrl: u32, // 0x20 + /// Alternate Function High Register + pub afrh: u32, // 0x24 +} + +/// Pin configuration details +#[derive(Debug)] +pub struct PinConfig { + pub mode: String, + pub otype: String, + pub speed: String, + pub pull: String, + pub input: String, + pub output: String, + pub lock: String, + pub alternate: String, +} + +impl fmt::Display for PinConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Use the write! macro to format the output to the formatter 'f' + write!( + f, + "{}:{}:{}:{}:{}:{}:{}:{}", + self.mode, + self.otype, + self.speed, + self.pull, + self.alternate, + self.lock, + self.input, + self.output + ) + } +} + +/// Represents the STM32H7 series of microcontrollers. +/// +/// This struct acts as a namespace for functions and data specific to the H7 series. +/// It can be expanded to implement a more generic `ChipSeries` trait in the future. +pub struct Stm32H7Series; + +impl Stm32H7Series { + /// Checks a list of matched parts for uniqueness based on gpio_count. + fn check_uniqueness( + matches: Vec<&'static Stm32H7Part>, + prefer_max_pins: bool, + ) -> Result<&'static Stm32H7Part, &'static str> { + if matches.len() == 1 { + return Ok(matches[0]); + } + + let first_gpio_count = matches[0].gpio_count; + if matches.iter().all(|p| p.gpio_count == first_gpio_count) { + Ok(matches[0]) + } else if prefer_max_pins { + matches + .into_iter() + .max_by_key(|p| p.gpio_count) + .ok_or("Failed to find part with max pins") + } else { + Err( + "Ambiguous match: Multiple parts found with different GPIO counts", + ) + } + } + + /// Finds a part by an identifier, checking fields in a specific order. + /// + /// Returns a known part from the database if found, or returns the UNKNOWN_PART + /// fallback for backward compatibility. The caller can use `is_known()` to + /// determine if the part is in the database. + pub fn find_part( + identifier: &str, + prefer_max_pins: bool, + ) -> &'static Stm32H7Part { + // Collect parts into a Vec to easily create new iterators. + let all_parts: Vec<&'static Stm32H7Part> = { + #[cfg(test)] + { + PARTS.iter().chain(TEST_ONLY_PARTS.iter()).collect() + } + #[cfg(not(test))] + { + PARTS.iter().collect() + } + }; + + // Level 1: Match by `ref_no` + let matches_ref_no: Vec<_> = all_parts + .iter() + .filter(|p| p.ref_no.eq_ignore_ascii_case(identifier)) + .cloned() + .collect(); + if !matches_ref_no.is_empty() + && let Ok(part) = + Self::check_uniqueness(matches_ref_no, prefer_max_pins) + { + return part; + } + + // Level 2: Match by `com_part_no` + let matches_com_part_no: Vec<_> = all_parts + .iter() + .filter(|p| p.com_part_no.eq_ignore_ascii_case(identifier)) + .cloned() + .collect(); + if !matches_com_part_no.is_empty() + && let Ok(part) = + Self::check_uniqueness(matches_com_part_no, prefer_max_pins) + { + return part; + } + + // Level 3: Match by `part_no` + let matches_part_no: Vec<_> = all_parts + .iter() + .filter(|p| p.part_no.eq_ignore_ascii_case(identifier)) + .cloned() + .collect(); + if !matches_part_no.is_empty() + && let Ok(part) = + Self::check_uniqueness(matches_part_no, prefer_max_pins) + { + return part; + } + + // Not found in database - return unknown part fallback for backward compatibility + &UNKNOWN_PART + } +} + +#[cfg(test)] +mod tests { + use super::*; + use zerocopy::AsBytes; + + #[test] + fn test_find_part_success() { + // Unique match on `ref_no` + let part = Stm32H7Series::find_part("STM32H753AIIx", false); + assert!(part.is_known()); + assert_eq!(part.ref_no(), "STM32H753AIIx"); + + // Unique match on `com_part_no` + let part = Stm32H7Series::find_part("STM32H743BGT6", false); + assert!(part.is_known()); + assert_eq!(part.com_part_no(), "STM32H743BGT6"); + + // Multiple matches, but same gpio_count + let part = Stm32H7Series::find_part("STM32H753XI", false); + assert!(part.is_known()); + assert_eq!(part.part_no(), "STM32H753XI"); + } + + #[test] + fn test_find_part_case_insensitive() { + // Test with lowercase identifier + let part = Stm32H7Series::find_part("stm32h753aiix", false); + assert!(part.is_known()); + assert_eq!(part.ref_no(), "STM32H753AIIx"); + + // Test with mixed case identifier + let part = Stm32H7Series::find_part("Stm32H743BGT6", false); + assert!(part.is_known()); + assert_eq!(part.com_part_no(), "STM32H743BGT6"); + } + + #[test] + fn test_part_not_found() { + // Returns unknown fallback for backward compatibility + let part = Stm32H7Series::find_part("STM32F407VGT6", false); + assert!(!part.is_known()); + assert_eq!(part.max_group(), 'K'); + assert_eq!(part.gpio_count(), 176); + } + + #[test] + fn test_ambiguous_match_with_max_pins_flag() { + // When ambiguous, prefer_max_pins flag selects highest pin count + let part = Stm32H7Series::find_part("STM32H753BI", true); + assert!(part.is_known()); + assert_eq!(part.gpio_count, 168); + } + + #[test] + fn test_pins_in_group() { + // Test STM32H753ZITx: 114 pins, groups A-H (8 groups) + // 114 / 16 = 7 full groups (A-G = 112 pins), group H has 2 pins + let part = Stm32H7Series::find_part("STM32H753ZITx", false); + assert_eq!(part.gpio_count(), 114); + assert_eq!(part.max_group(), 'H'); + + // Groups A-G should have all 16 pins + for group in 'A'..='G' { + assert_eq!( + part.pins_in_group(group), + 16, + "Group {} should have 16 pins", + group + ); + } + // Group H should have 2 pins (114 - 112 = 2) + assert_eq!(part.pins_in_group('H'), 2); + + // Groups beyond H should have 0 pins + assert_eq!(part.pins_in_group('I'), 0); + assert_eq!(part.pins_in_group('J'), 0); + + // Test STM32H753VIT6: 82 pins, groups A-F (6 groups) + // 82 / 16 = 5 full groups (A-E = 80 pins), group F has 2 pins + let part = Stm32H7Series::find_part("STM32H753VIT6", false); + assert_eq!(part.gpio_count(), 82); + assert_eq!(part.max_group(), 'F'); + + // Groups A-E should have all 16 pins + for group in 'A'..='E' { + assert_eq!( + part.pins_in_group(group), + 16, + "Group {} should have 16 pins", + group + ); + } + // Group F should have 2 pins (82 - 80 = 2) + assert_eq!(part.pins_in_group('F'), 2); + + // Groups beyond F should have 0 pins + assert_eq!(part.pins_in_group('G'), 0); + } + + #[test] + fn interpret_config_data() { + use crate::stm32h7_testdata::GPIO_TEST_DATA; + + let partname = "STM32H753XI"; + let part = Stm32H7Series::find_part(partname, false); + assert!(part.is_known(), "Lookup failed for {}", partname); + assert_eq!(part.gpio_count(), 172); + assert_eq!(part.groups(), 11); + assert_eq!(part.max_group(), 'K'); + + for test_data in GPIO_TEST_DATA.iter() { + let result = part.gpio_register_block_address(test_data.group); + assert!( + result.is_some(), + "gpio_register_block_address failed for {} group {}", + partname, + test_data.group + ); + let addr = result.unwrap(); + assert_eq!(addr, test_data.addr); + + for pin in 0u8..16u8 { + let result = Stm32H7Part::get_pin_config( + test_data.config_data.as_bytes(), + pin, + ); + assert!( + result.is_ok(), + "get_pin_config failed for {}:{}:{}", + partname, + test_data.group, + pin + ); + let pin_config = result.unwrap(); + assert_eq!( + pin_config.to_string(), + test_data.expected_outputs[pin as usize], + "Group:{} Pin:{}", + test_data.group, + pin + ); + } + } + } +} diff --git a/cmd/gpio/src/stm32h7_testdata.rs b/cmd/gpio/src/stm32h7_testdata.rs new file mode 100644 index 00000000..3f56bca0 --- /dev/null +++ b/cmd/gpio/src/stm32h7_testdata.rs @@ -0,0 +1,394 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Test data for STM32H7 GPIO configuration tests. +//! This file contains GPIO register block test data. + +#![cfg(test)] + +use super::stm32h7_parts::GpioRegisters; + +pub struct TestData { + pub group: char, + pub addr: u32, + pub config_data: &'static GpioRegisters, + pub expected_outputs: [&'static str; 16], +} + +pub const GPIO_TEST_DATA: &[TestData] = &[ + TestData { + group: 'A', + addr: 0x58020000, + config_data: &GpioRegisters { + moder: 0x6aabbffb, + otyper: 0x00008000, + ospeedr: 0x0c00c00c, + pupdr: 0x24000000, + idr: 0x0000c600, + odr: 0x00008000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xb00000b0, + afrh: 0x00077770, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:PullUp:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:PullDown:AF0:Unlocked:InOne:OutZero", + "Output:OutOpenDrain:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + ], + }, + TestData { + group: 'B', + addr: 0x58020400, + config_data: &GpioRegisters { + moder: 0x47fa90b7, + otyper: 0x00000342, + ospeedr: 0x2000cac0, + pupdr: 0x00000000, + idr: 0x000043f0, + odr: 0x00000340, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xc0000000, + afrh: 0x55500044, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutOpenDrain:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Input:OutPushPull:HighSpeed:NoPull:AF0:Unlocked:InOne:OutZero", + "Input:OutPushPull:HighSpeed:NoPull:AF0:Unlocked:InOne:OutZero", + "Output:OutOpenDrain:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutOpenDrain:LowSpeed:NoPull:AF4:Unlocked:InOne:OutOne", + "Alternate:OutOpenDrain:LowSpeed:NoPull:AF4:Unlocked:InOne:OutOne", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'C', + addr: 0x58020800, + config_data: &GpioRegisters { + moder: 0xffffdadf, + otyper: 0x00000000, + ospeedr: 0x00002f00, + pupdr: 0x00000000, + idr: 0x00000004, + odr: 0x00000004, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00bb0000, + afrh: 0x00000000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Output:OutPushPull:HighSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'D', + addr: 0x58020c00, + config_data: &GpioRegisters { + moder: 0xaaaaaaba, + otyper: 0x00000000, + ospeedr: 0xffffffcf, + pupdr: 0x00001000, + idr: 0x0000c7bb, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xccccc0cc, + afrh: 0xcccccccc, + }, + expected_outputs: [ + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:PullUp:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + ], + }, + TestData { + group: 'E', + addr: 0x58021000, + config_data: &GpioRegisters { + moder: 0xaaaaa9aa, + otyper: 0x00000000, + ospeedr: 0xffffc8cf, + pupdr: 0x00000000, + idr: 0x0000ffb0, + odr: 0x00000010, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xc550c5cc, + afrh: 0xcccccccc, + }, + expected_outputs: [ + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Alternate:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + ], + }, + TestData { + group: 'F', + addr: 0x58021400, + config_data: &GpioRegisters { + moder: 0xffeaafcf, + otyper: 0x00000000, + ospeedr: 0x00155020, + pupdr: 0x00000010, + idr: 0x00000044, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x99000000, + afrh: 0x000009aa, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Input:OutPushPull:HighSpeed:PullUp:AF0:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF9:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF9:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF10:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF10:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF9:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'G', + addr: 0x58021800, + config_data: &GpioRegisters { + moder: 0xfabfefff, + otyper: 0x00000000, + ospeedr: 0x0fc01000, + pupdr: 0x00000000, + idr: 0x00000040, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x0a000000, + afrh: 0x00bbb000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF10:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'H', + addr: 0x58021c00, + config_data: &GpioRegisters { + moder: 0xffffffff, + otyper: 0x00000000, + ospeedr: 0x00000000, + pupdr: 0x00000000, + idr: 0x00000000, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00000000, + afrh: 0x00000000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'I', + addr: 0x58022000, + config_data: &GpioRegisters { + moder: 0x7fffffa9, + otyper: 0x00000000, + ospeedr: 0x00000020, + pupdr: 0x00000000, + idr: 0x00008001, + odr: 0x00008001, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00005550, + afrh: 0x00000000, + }, + expected_outputs: [ + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + ], + }, + TestData { + group: 'J', + addr: 0x58022400, + config_data: &GpioRegisters { + moder: 0xfffaffff, + otyper: 0x00000000, + ospeedr: 0x00000000, + pupdr: 0x00000000, + idr: 0x00000300, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00000000, + afrh: 0x00000088, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF8:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF8:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'K', + addr: 0x58022800, + config_data: &GpioRegisters { + moder: 0xffffffff, + otyper: 0x00000000, + ospeedr: 0x00000000, + pupdr: 0x00000000, + idr: 0x00000000, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00000000, + afrh: 0x00000000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, +]; From d5608f4cfce3e69d39e1b8c06c95c4bf26bde01d Mon Sep 17 00:00:00 2001 From: Ben Stoltz Date: Wed, 15 Oct 2025 22:49:39 -0700 Subject: [PATCH 7/7] Change PinConfig to reduce boilerplate `to_string` calls. --- cmd/gpio/src/stm32h7_parts.rs | 52 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/cmd/gpio/src/stm32h7_parts.rs b/cmd/gpio/src/stm32h7_parts.rs index b393f550..e29070de 100644 --- a/cmd/gpio/src/stm32h7_parts.rs +++ b/cmd/gpio/src/stm32h7_parts.rs @@ -167,25 +167,27 @@ impl Stm32H7Part { _ => unreachable!(), }; - let alternate = format!( - "AF{}", - if pin < 8 { - let reg = rb.afrl; - (reg >> (pin * 4)) & 15 - } else { - let reg = rb.afrh; - (reg >> ((pin - 8) * 4)) & 15 - } - ); + let af_value: &[&'static str] = &[ + "AF0", "AF1", "AF2", "AF3", "AF4", "AF5", "AF6", "AF7", "AF8", + "AF9", "AF10", "AF11", "AF12", "AF13", "AF14", "AF15", + ]; + + let alternate = af_value[if pin < 8 { + let reg = rb.afrl; + (reg >> (pin * 4)) & 15 + } else { + let reg = rb.afrh; + (reg >> ((pin - 8) * 4)) & 15 + } as usize]; Ok(PinConfig { - mode: mode.to_string(), - otype: otype.to_string(), - speed: speed.to_string(), - pull: pull.to_string(), - input: input.to_string(), - output: output.to_string(), - lock: lock.to_string(), + mode, + otype, + speed, + pull, + input, + output, + lock, alternate, }) } @@ -500,14 +502,14 @@ pub struct GpioRegisters { /// Pin configuration details #[derive(Debug)] pub struct PinConfig { - pub mode: String, - pub otype: String, - pub speed: String, - pub pull: String, - pub input: String, - pub output: String, - pub lock: String, - pub alternate: String, + pub mode: &'static str, + pub otype: &'static str, + pub speed: &'static str, + pub pull: &'static str, + pub input: &'static str, + pub output: &'static str, + pub lock: &'static str, + pub alternate: &'static str, } impl fmt::Display for PinConfig {