From 87fcc6eadd80b9ad27e1fdfa189f66a0bef70fbf Mon Sep 17 00:00:00 2001 From: Alan Robertson Date: Fri, 7 Nov 2025 07:19:21 -0700 Subject: [PATCH 1/5] Added blank line ffor IntelliJ IDEA ;-) --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 2dda505..acba25f 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ // hardware for the benefit of mock tests. Ability to inject faults and control // contents of hardware registers will be added as needed for tests. // + use embedded_hal::digital::OutputPin; use embedded_hal::spi::{Mode, Phase, Polarity, SpiBus}; extern crate alloc; From 6f10d95bc66fa2a7c8b932d7e76d873ede106ed7 Mon Sep 17 00:00:00 2001 From: Alan Robertson Date: Fri, 7 Nov 2025 07:48:41 -0700 Subject: [PATCH 2/5] removed obsolete and misplaced FIXME --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index acba25f..b6a0a52 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ // 5. Instruct user to remove probe from the bath // 6. Display temperatures every second in a loop, // which stops after 5 minutes, or when the temperature reaches -// 60 degrees F or so.// FIXME: figure out what to do about this... +// 60 degrees F or so. // // TODO: Stub off hardware access by creating abstract implementations of Trait(s) and // create minimal Mock unit tests to validate basic abstract operations. From a40e858334063718769c0186eb44b0ffb755dde4 Mon Sep 17 00:00:00 2001 From: Alan Robertson Date: Fri, 14 Nov 2025 16:01:47 -0700 Subject: [PATCH 3/5] Switched to one-shot to get continually updated results --- Cargo.toml | 23 ++- examples/ice_bath_test.rs | 311 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 59 ++++++-- 3 files changed, 364 insertions(+), 29 deletions(-) create mode 100644 examples/ice_bath_test.rs diff --git a/Cargo.toml b/Cargo.toml index 874bd43..e6ceb70 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "simple_max31865" version = "1.0.0" -authors = ["Rudi Horn ", "Emeric Martineau <11473190+emeric-martineau@users.noreply.github.com>"] +authors = ["Alan Robertson "] keywords = ["embedded-hal-v1", "rtd-sensor", "max31865"] categories = ["embedded", "no-std", "hardware-support"] -description = "Simplified driver for the MAX31865 RTD to Digital converter (Raspberry Pi focus)" +description = "Easy-to-use driver for the MAX31865 RTD to Digital converter (Raspberry Pi focus)" documentation = "https://github.com/emeric-martineau/rs-max31865" repository = "https://github.com/emeric-martineau/rs-max31865.git" readme = "README.md" @@ -15,17 +15,14 @@ edition = "2021" rppal = { version = "0.17", features = ["embedded-hal"] } # Enables embedded-hal v1 traits for SpiBus, OutputPin, etc. embedded-hal = "1.0" # Ensure v1 -[target.thumbv7m-none-eabi.dev-dependencies] -cortex-m-semihosting = "0.3.7" -cortex-m = "0.7.2" -cortex-m-rt = "0.6.13" -panic-halt = "0.2.0" -stm32f1xx-hal = { version = "0.7.0", features = ["stm32f103"] } -embedded-graphics = "0.6.2" -ssd1306 = "0.5.1" - -#[target.'cfg(unix)'.dev-dependencies] -#linux-embedded-hal = "0.3.0" +[dev-dependencies] +clap = { version = "4.0", features = ["derive"] } # For CLI args in examples + +[[example]] +name = "ice_bath_test" +path = "examples/ice_bath_test.rs" +# required-features = ["doc"] # If you want to tie it to the doc feature + [profile.test] lto = "thin" # Enables link-time optimization: Strips unused symbols (e.g., test harness bloat) like in your main binary diff --git a/examples/ice_bath_test.rs b/examples/ice_bath_test.rs new file mode 100644 index 0000000..d2ce011 --- /dev/null +++ b/examples/ice_bath_test.rs @@ -0,0 +1,311 @@ +//! Ice Bath Manual Test for MAX31865 RTD Driver +//! Verifies sensor accuracy: ~32°F in ice bath, recovery to room temp. +//! Run on Raspberry Pi; follow prompts. +//! +//! Supports CLI args (e.g., --cs-pin 24), env vars (e.g., MAX31865_CS_PIN=24), and defaults. +//! CLI: cargo run --example ice_bath_test -- --cs-pin 24 --leads Three --filter Sixty +//! Env: export MAX31865_CS_PIN=24; export MAX31865_LEADS=Three; export MAX31865_FILTER=Sixty +//! MAX31865_CS_PIN: u8 (e.g., 24). +// MAX31865_LEADS: String parsed to enum (e.g., Three or 3). +// MAX31865_FILTER: String parsed to enum (e.g., 50 or Fifty). +// Precedence: CLI arg > env var > default. + +use simple_max31865::{decode_fault_status, FilterHz, RTDLeads, RTDReader, RtdError}; +use std::env; +use std::io; +use std::time::{Duration, Instant}; + +/// Parse RTDLeads from string (e.g., "Three", "3", "two"). +fn parse_rtd_leads(s: &str) -> Result { + match s.to_lowercase().as_str() { + "two" | "2" => Ok(RTDLeads::Two), + "three" | "3" => Ok(RTDLeads::Three), + "four" | "4" => Ok(RTDLeads::Four), + _ => Err(format!("Invalid leads: {}. Must be Two/2, Three/3, or Four/4.", s)), + } +} + +/// Parse FilterHz from string (e.g., "Sixty", "60", "fifty"). +fn parse_filter_hz(s: &str) -> Result { + match s.to_lowercase().as_str() { + "fifty" | "50" => Ok(FilterHz::Fifty), + "sixty" | "60" => Ok(FilterHz::Sixty), + _ => Err(format!("Invalid filter: {}. Must be Fifty/50 or Sixty/60.", s)), + } +} + +fn main() -> Result<(), Box> { + // Parse CLI args (simple manual scan for --key value; cargo passes after --) + let args: Vec = std::env::args().collect(); + let mut cli_cs_pin: Option = None; + let mut cli_leads: Option = None; + let mut cli_filter: Option = None; + + let mut i = 1; // Skip binary name (args[0]) + while i < args.len() { + if args[i].starts_with("--") { + let key = &args[i][2..]; // Strip "--" + if i + 1 >= args.len() { + return Err(format!("--{} requires a value", key).into()); + } + let value = &args[i + 1]; + match key { + "cs-pin" => { + cli_cs_pin = Some(value.parse().map_err(|e| format!("Invalid --cs-pin '{}': {}", value, e))?); + } + "leads" => { + cli_leads = Some(parse_rtd_leads(value).map_err(|e| format!("Invalid --leads '{}': {}", value, e))?); + } + "filter" => { + cli_filter = Some(parse_filter_hz(value).map_err(|e| format!("Invalid --filter '{}': {}", value, e))?); + } + _ => { + eprintln!("Unknown CLI arg: --{}", key); + eprintln!("Usage: --cs-pin | --leads | --filter "); + std::process::exit(1); + } + } + i += 2; // Skip key + value + } else { + eprintln!("Unexpected arg: {}", args[i]); + i += 1; + } + } + + // Fallback to env vars, then defaults (CLI > env > default) + let cs_pin_str = cli_cs_pin.map(|p| p.to_string()) + .or_else(|| env::var("MAX31865_CS_PIN").ok()) + .unwrap_or_else(|| "8".to_string()); + let cs_pin: u8 = cs_pin_str.parse().map_err(|e| format!("Invalid CS_PIN '{}': {}", cs_pin_str, e))?; + + let leads_str = cli_leads.map(|l| match l { + RTDLeads::Two => "Two".to_string(), + RTDLeads::Three => "Three".to_string(), + RTDLeads::Four => "Four".to_string(), + }).or_else(|| env::var("MAX31865_LEADS").ok()) + .unwrap_or_else(|| "Three".to_string()); + let leads = parse_rtd_leads(&leads_str).map_err(|e| format!("Invalid LEADS '{}': {}", leads_str, e))?; + + let filter_str = cli_filter.map(|f| match f { + FilterHz::Fifty => "Fifty".to_string(), + FilterHz::Sixty => "Sixty".to_string(), + }).or_else(|| env::var("MAX31865_FILTER").ok()) + .unwrap_or_else(|| "Sixty".to_string()); + let filter = parse_filter_hz(&filter_str).map_err(|e| format!("Invalid FILTER '{}': {}", filter_str, e))?; + + println!("=== MAX31865 Ice Bath Test ==="); + println!("This test verifies your RTD sensor accuracy using an ice bath (~32°F/0°C)."); + println!("It checks initial room temp, monitors cooling in the bath, and recovery after removal."); + println!("Safety: Ensure probe is fully submerged (stir ice/water mix); keep water away from electronics."); + println!("Expected: Temps stabilize near 32°F in bath (±2°F ok), rise to 60°F+ after ~1-2 min."); + println!("If temps don't change or faults occur, check wiring/probe."); + println!("Config: CS pin={}, {} leads, {} Hz filter", cs_pin, leads as u8, match filter { FilterHz::Fifty => 50, _ => 60 }); + println!("(From CLI/env vars: --cs-pin/--leads/--filter or MAX31865_*; defaults used if unset)"); + println!(); + + let mut reader = RTDReader::new(cs_pin, leads, filter) + .map_err(|e| format!("Failed to init RTDReader: {:?}", e))?; + + // Step 1: Validate initial room temp (40-110°F) + println!("Step 1: Measuring initial room temperature..."); + let raw_result = reader.get_raw_data(); + let raw = match raw_result { + Ok(r) => r, + Err(e) => { + let status = reader.read_fault_status().unwrap_or(0); + eprintln!("Raw read failed: {:?}. Fault status: 0x{:02X} -> {:?}", e, status, decode_fault_status(status)); + return Err(format!("Raw read failed in initial phase: {:?}", e).into()); + } + }; + let resistance_result = reader.get_resistance(); + let resistance = match resistance_result { + Ok(r) => r, + Err(e) => { + let status = reader.read_fault_status().unwrap_or(0); + eprintln!("Resistance read failed: {:?}. Last raw: 0x{:04X} (LSB fault: {}). Fault status: 0x{:02X} -> {:?}", e, raw, raw & 1, status, decode_fault_status(status)); + return Err(format!("Resistance read failed in initial phase: {:?}", e).into()); + } + }; + let temp_result = reader.get_temperature(); + let temp_c = match temp_result { + Ok(t) => t, + Err(e) => { + let status = reader.read_fault_status().unwrap_or(0); + eprintln!("Temperature read failed: {:?}. Raw: 0x{:04X} (LSB fault: {}), Resistance: {:.2} ohms. Fault status: 0x{:02X} -> {:?}", e, raw, raw & 1, resistance, status, decode_fault_status(status)); + return Err(format!("Temperature read failed in initial phase: {:?}", e).into()); + } + }; + let temp_f = temp_c * 9.0 / 5.0 + 32.0; + + println!("Raw RTD value: 0x{:04X} (LSB fault bit: {})", raw, raw & 1); + println!("Resistance: {:.2} ohms", resistance); + println!("Temperature: {:.1}°F ({:.1}°C)", temp_f, temp_c); + + if temp_f < 40.0 || temp_f > 110.0 { + eprintln!("Initial temp {:.1}°F out of range (expected 40-110°F).", temp_f); + eprintln!("Raw: 0x{:04X} (LSB fault: {}) | Resistance: {:.2} ohms | Temp: {:.1}°C", raw, raw & 1, resistance, temp_c); + eprintln!("Likely wiring/sensor issue: Check RTD connections (expected ~108 ohms at room temp, raw ~0x6A80)."); + eprintln!("If raw ~0x0000 or resistance ~0 ohms: Short circuit (check for solder bridges)."); + eprintln!("If raw ~0x7FFF or resistance very high: Open circuit (check wire continuity)."); + return Err("Aborting test—fix hardware first.".into()); + } + println!("Initial temp: {:.1}°F ({:.1}°C) - OK to proceed.", temp_f, temp_c); + let initial_temp_f = temp_f; // Save for summary + println!(); + + // Step 2: Ice Bath Phase + println!("Step 2: Prepare ice bath (ice + water, stirred). Submerge probe fully."); + println!("Press Enter when probe is in the bath and ready..."); + io::stdin().read_line(&mut String::new())?; + println!("Starting bath monitoring (stops at <=35°F or 5 min)..."); + let bath_start = Instant::now(); + let mut min_temp_f = f64::INFINITY; + let mut consecutive_errors = 0; + let mut last_temp_f = initial_temp_f; // For stuck detection + let mut stuck_warning = false; + loop { + if consecutive_errors >= 5 { + return Err("Too many consecutive read errors (5+). Aborting test—check hardware.".into()); + } + + match read_temp_with_faults(&mut reader, "Bath") { + Ok(temp_f) => { + consecutive_errors = 0; // Reset on success + min_temp_f = min_temp_f.min(temp_f); + let elapsed = bath_start.elapsed().as_secs(); + let temp_c = (temp_f - 32.0) * 5.0 / 9.0; + println!("Elapsed: {}s | Temp: {:.1}°F ({:.1}°C) | Min so far: {:.1}°F", elapsed, temp_f, temp_c, min_temp_f); + + // Simple stuck check: Warn if no change for 10s (approx 10 reads) + if (temp_f - last_temp_f).abs() < 0.1 && elapsed > 10 && !stuck_warning { + println!("Warning: Temperature not changing—stir bath or check for stuck reads."); + stuck_warning = true; + } + last_temp_f = temp_f; + + if temp_f <= 35.0 || elapsed >= 300 { + break; + } + } + Err(e) => { + consecutive_errors += 1; + eprintln!("Read error (#{}) in bath phase: {}", consecutive_errors, e); + if consecutive_errors >= 3 { + eprintln!("Multiple errors—consider aborting if persists."); + } + } + } + + std::thread::sleep(Duration::from_secs(1)); + } + let bath_min_c = (min_temp_f - 32.0) * 5.0 / 9.0; + let reason = if bath_start.elapsed().as_secs() >= 300 { "timeout" } else { "threshold hit" }; + println!("\nBath phase done ({}). Min temp: {:.1}°F ({:.1}°C)", reason, min_temp_f, bath_min_c); + if min_temp_f > 34.0 { + println!("Note: Expected ~32°F; yours was higher—ensure full immersion/stirring."); + } + println!(); + + // Step 3: Removal/Recovery Phase + println!("Step 3: Remove probe from bath and let it air-dry/warm to room temp."); + println!("Press Enter when probe is removed and ready..."); + io::stdin().read_line(&mut String::new())?; + println!("Starting recovery monitoring (stops at >=60°F or 5 min)..."); + let recovery_start = Instant::now(); + let mut max_temp_f = min_temp_f; + consecutive_errors = 0; + last_temp_f = min_temp_f; + stuck_warning = false; + loop { + if consecutive_errors >= 5 { + return Err("Too many consecutive read errors (5+) in recovery. Aborting.".into()); + } + + match read_temp_with_faults(&mut reader, "Recovery") { + Ok(temp_f) => { + consecutive_errors = 0; + max_temp_f = max_temp_f.max(temp_f); + let elapsed = recovery_start.elapsed().as_secs(); + let temp_c = (temp_f - 32.0) * 5.0 / 9.0; + println!("Elapsed: {}s | Temp: {:.1}°F ({:.1}°C) | Max so far: {:.1}°F", elapsed, temp_f, temp_c, max_temp_f); + + // Stuck check + if (temp_f - last_temp_f).abs() < 0.1 && elapsed > 10 && !stuck_warning { + println!("Warning: Temperature not changing—ensure probe is exposed to air."); + stuck_warning = true; + } + last_temp_f = temp_f; + + if temp_f >= 60.0 || elapsed >= 300 { + break; + } + } + Err(e) => { + consecutive_errors += 1; + eprintln!("Read error (#{}) in recovery phase: {}", consecutive_errors, e); + } + } + + std::thread::sleep(Duration::from_secs(1)); + } + let final_temp_f = read_temp_with_faults(&mut reader, "Final")?; + let final_temp_c = (final_temp_f - 32.0) * 5.0 / 9.0; + let reason = if recovery_start.elapsed().as_secs() >= 300 { "timeout" } else { "threshold hit" }; + println!("\nRecovery phase done ({}). Final temp: {:.1}°F ({:.1}°C)", reason, final_temp_f, final_temp_c); + println!(); + + // Summary + println!("=== Test Summary ==="); + println!("Initial: {:.1}°F", initial_temp_f); + println!("Bath min: {:.1}°F (expected ~32°F)", min_temp_f); + println!("Recovery final: {:.1}°F (expected near initial)", final_temp_f); + if min_temp_f < 30.0 || min_temp_f > 34.0 { + println!("Potential issue: Bath temp off from 32°F. Recheck setup or compare to reference code."); + } else { + println!("Test passed! Sensor behaving as expected."); + } + println!("If stuck temps or faults occurred, inspect hardware/logs."); + + Ok(()) +} + +/// Read temp in °F, check/clear faults, log any. Returns Err on hard failure (after recovery attempt). +fn read_temp_with_faults(reader: &mut RTDReader, phase: &str) -> Result> { + // Read temp + let temp_c = match reader.get_temperature() { + Ok(t) => Ok(t), + Err(e) => { + // Attempt recovery for MAX faults + if reader.is_max_fault(&e) { + match reader.read_fault_status() { + Ok(status) if status != 0 => { + let faults = decode_fault_status(status); + eprintln!("MAX fault in {} phase: {:?} (clearing...)", phase, faults); + if let Err(clear_e) = reader.clear_fault() { + eprintln!("Failed to clear fault: {:?}", clear_e); + } + // Retry the temp read once after clear + match reader.get_temperature() { + Ok(t) => Ok(t), + Err(retry_e) => Err(format!("Retry failed after MAX fault: {:?}", retry_e)), + } + } + _ => Err(format!("MAX fault during read (status unavailable): {:?}", e)), + } + } else { + Err(format!("Hard read error in {} phase: {:?}", phase, e)) + } + } + }?; + + let temp_f = temp_c * 9.0 / 5.0 + 32.0; + // Double-check faults post-read (non-blocking) + if let Ok(status) = reader.read_fault_status() { + if status != 0 { + let faults = decode_fault_status(status); + eprintln!("Post-read fault in {} phase: {:?} (cleared)", phase, faults); + let _ = reader.clear_fault(); // Ignore clear error + } + } + Ok(temp_f) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b6a0a52..4cd04b9 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ // 5. Instruct user to remove probe from the bath // 6. Display temperatures every second in a loop, // which stops after 5 minutes, or when the temperature reaches -// 60 degrees F or so. +// 60 degrees F or so.// FIXME: figure out what to do about this... // // TODO: Stub off hardware access by creating abstract implementations of Trait(s) and // create minimal Mock unit tests to validate basic abstract operations. @@ -67,7 +67,6 @@ // hardware for the benefit of mock tests. Ability to inject faults and control // contents of hardware registers will be added as needed for tests. // - use embedded_hal::digital::OutputPin; use embedded_hal::spi::{Mode, Phase, Polarity, SpiBus}; extern crate alloc; @@ -208,7 +207,7 @@ pub mod rtd_reader { InternalError::GpioError => "NCS pin setup failed".to_string(), _ => "MAX31865 init failed".to_string(), }))?; - inner.configure(true, true, leads, filter) + inner.configure(leads, filter) .map_err(|e| RtdError::Init(format!("Configure failed: {:?}", e)))?; Ok(RTDReader { inner }) @@ -296,6 +295,7 @@ mod private { spi: SPI, ncs: NCS, calibration: u32, + base_config: u8, // Set in configure } impl Max31865 @@ -311,17 +311,28 @@ mod private { let max31865 = Max31865 { spi, ncs, - calibration: default_calib, /* value in ohms multiplied by 100 */ + calibration: default_calib, + base_config: 0, // Set in configure }; Ok(max31865) } + // From MAX31865 datasheet (page 16, Table 8): + // + // Bit 7 (V_BIAS): 1 = enable bias excitation (should be 1). + // Bit 6 (1-SHOT): 0 = continuous conversion (ongoing reads), + // 1 = one-shot (single conversion, then stop). + // Bit 5: Reserved (should be 0) + // Bit 4 (wires): 1 = 3-wire PT100, 0 = 2 or 4-wire + // Bit 3 (AUTO-CONVERT): 1 = auto-conversion enabled + // Bit 2: Reserved (should be 0) + // Bit 1: Reserved (should be 0) + // Bit 0 (50/60Hz): 0 = 60Hz, 1 = 50 hz + /// Updates the devices configuration (internal use only). pub fn configure( &mut self, - vbias: bool, - conversion_mode: bool, sensor_type_enum: RTDLeads, // From public RTDLeads cast filter_mode_enum: FilterHz, // From public FilterHz cast ) -> Result<(), Error> { @@ -335,12 +346,12 @@ mod private { FilterHz::Fifty => 1u8, // Fifty = 1 (low order bit) FilterHz::Sixty => 0u8, // Sixty = 0 (no lower order bits) }; - let conf: u8 = ((vbias as u8) << 7) - | ((conversion_mode as u8) << 6) - | (sensor_type << 4) // Bit 4: sensor type (0 for 2/4-wire, 1 for 3-wire) - | filter_mode; // Bit 0: filter (0 for 60Hz, 1 for 50Hz) - - self.write(Register::CONFIG, conf)?; + // One-shot config: V_BIAS=1, 1-SHOT=1, wires, filter (no AUTO= bit 3=0) + self.base_config = (1u8 << 7) // V_BIAS=1 + | (1u8 << 6) // 1-SHOT=1 (triggers on write) + | (sensor_type << 4) // Wires bit 4 + | filter_mode; // Filter bit 0 + self.write(Register::CONFIG, self.base_config)?; // Initial write (starts first conversion) self.clear_fault()?; // Unlatch any boot faults (mimics Adafruit init) Ok(()) @@ -398,10 +409,26 @@ mod private { /// resistor). See manual for further information. /// The last bit specifies if the conversion was successful. pub fn read_raw(&mut self) -> Result { - let buffer = self.read_two(Register::RTD_MSB)?; // Single read_two on MSB clocks MSB + LSB - let raw = ((buffer[0] as u16) << 8) | (buffer[1] as u16); // buffer[0] = MSB, [1] = LSB - if raw & 1 != 0 { // LSB bit 0 = 1 = fault during read - return Err(Error::MAXError); + // Trigger new conversion: Write config (1-SHOT=1 starts it) + self.write(Register::CONFIG, self.base_config)?; + + // Wait for conversion (100ms conservative >65ms datasheet min) + std::thread::sleep(std::time::Duration::from_millis(100)); + + // Read RTD + let buffer = self.read_two(Register::RTD_MSB)?; + let raw = ((buffer[0] as u16) << 8) | (buffer[1] as u16); + if raw & 1 != 0 { // Fault: Clear + retry once + let _ = self.read_fault_status(); // Reads + clears faults + // Retry: Trigger again + self.write(Register::CONFIG, self.base_config)?; + std::thread::sleep(std::time::Duration::from_millis(100)); + let retry_buffer = self.read_two(Register::RTD_MSB)?; + let retry_raw = ((retry_buffer[0] as u16) << 8) | (retry_buffer[1] as u16); + if retry_raw & 1 != 0 { + return Err(Error::MAXError); // Retry failed + } + return Ok(retry_raw); } Ok(raw) } From 7ddd1753f2f68d41494f07d560994f7865c9e2a7 Mon Sep 17 00:00:00 2001 From: Alan Robertson Date: Fri, 14 Nov 2025 17:09:20 -0700 Subject: [PATCH 4/5] Fixed minor IntelliJ complaints --- .not-cargo/config | 31 --------------------------- .travis.yml | 39 ---------------------------------- Cargo.toml | 2 +- README.md | 15 +++++++------- gen-examples.sh | 53 ----------------------------------------------- src/lib.rs | 33 ++++++++++------------------- 6 files changed, 20 insertions(+), 153 deletions(-) delete mode 100644 .not-cargo/config delete mode 100755 .travis.yml delete mode 100755 gen-examples.sh diff --git a/.not-cargo/config b/.not-cargo/config deleted file mode 100644 index 4d5d9d2..0000000 --- a/.not-cargo/config +++ /dev/null @@ -1,31 +0,0 @@ -[target.thumbv6m-none-eabi] -runner = 'gdb-multiarch' -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "linker=arm-none-eabi-ld", -] - -[target.thumbv7m-none-eabi] -runner = 'gdb-multiarch' -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "linker=arm-none-eabi-ld", -] - -[target.thumbv7em-none-eabi] -runner = 'gdb-multiarch' -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "linker=arm-none-eabi-ld", -] - -[target.thumbv7em-none-eabihf] -runner = 'gdb-multiarch' -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "linker=arm-none-eabi-ld", -] - -[target.arm-unknown-linux-gnueabihf] -runner = 'gdb-multiarch' -linker = 'arm-linux-gnueabihf-gcc' diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index e8e4408..0000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -language: rust - -matrix: - include: - - env: TARGET=x86_64-unknown-linux-gnu - rust: nightly - - env: TARGET=thumbv7m-none-eabi - rust: nightly - addons: - apt: - packages: - - gcc-arm-none-eabi - -before_install: set -e - -install: - - bash ci/install.sh - -script: - - bash ci/script.sh - -after_script: set +e - -after_success: - - bash ci/after_success.sh - -cache: cargo -before_cache: - # Travis can't cache files that are not readable by "others" - - chmod -R a+r $HOME/.cargo - -branches: - only: - - master - - extra_examples - -notifications: - email: - on_success: never diff --git a/Cargo.toml b/Cargo.toml index e6ceb70..69ee9d1 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ keywords = ["embedded-hal-v1", "rtd-sensor", "max31865"] categories = ["embedded", "no-std", "hardware-support"] description = "Easy-to-use driver for the MAX31865 RTD to Digital converter (Raspberry Pi focus)" documentation = "https://github.com/emeric-martineau/rs-max31865" -repository = "https://github.com/emeric-martineau/rs-max31865.git" +repository = "https://github.com/Alan-R/simple-max31865-rs" readme = "README.md" license = "MIT OR Apache-2.0" edition = "2021" diff --git a/README.md b/README.md index 3a504a0..f20265c 100755 --- a/README.md +++ b/README.md @@ -4,21 +4,22 @@ A generic driver for the MAX31865 RTD to Digital converter ## [Documentation](https://rudihorn.github.io/max31865/max31865/index.html) -## [Examples](https://github.com/rudihorn/max31865/tree/extra_examples/examples) +## [Examples](https://github.com/Alan-R/simple-max31865-rs/tree/updates/examples) +Examples are in the *examples* directory from the root of the tree. ## What works -- reading the raw value and the converted temperature value +- reading the raw value and the converted temperature value either in f64 or scaled integer forms. +- Detecting and recovering from errors - setting the ohmic calibration value -- configuring V_BIAS, one shot, filter frequency +- Interaction with the chip works well. +- Reading temperatures and resistances from PT-100 sensors. ## TODO -- [ ] Fault tolerance / detection / status +- Working with the PT-1000 sensors +- Many other thoughts listed in the src/lib.rs file. -## Examples - -There is an example for the Raspberry pi in the examples directory. ## License diff --git a/gen-examples.sh b/gen-examples.sh deleted file mode 100755 index 9196fbb..0000000 --- a/gen-examples.sh +++ /dev/null @@ -1,53 +0,0 @@ -# Converts the examples in the `examples` directory into documentation in the -# `examples` module (`src/examples/*.rs`) - -set -euxo pipefail - -main() { - local examples=( - stm32 - stm32_ssd1306 - rpi - ) - - rm -rf src/examples - - mkdir src/examples - - cat >src/examples/mod.rs <<'EOF' -//! Examples -//! -//! In order of increasing complexity -// Auto-generated. Do not modify. -EOF - - local i=0 out= - for ex in ${examples[@]}; do - name=_$(printf "%02d" $i)_${ex//-/_} - out=src/examples/${name}.rs - - echo "pub mod $name;" >> src/examples/mod.rs - - grep '//!' examples/$ex.rs > $out - echo '//!' >> $out - echo '//! ```' >> $out - grep -v '//!' examples/$ex.rs | ( - IFS='' - - while read line; do - echo "//! $line" >> $out; - done - ) - echo '//! ```' >> $out - echo '// Auto-generated. Do not modify.' >> $out - - - chmod -x $out - - i=$(( i + 1 )) - done - - chmod -x src/examples/mod.rs -} - -main diff --git a/src/lib.rs b/src/lib.rs index 4cd04b9..7da63ad 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,26 +3,15 @@ //! # References //! - Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf //! - Wiring diagrams: https://www.playingwithfusion.com/docs/1203 +//! - SPECIAL NOTE: The chip does _not_ implement continuous mode, in spite of the docs. //! -// TODO: Update and improve README (see other branches), esp sample code +// TODO: Update and improve README (see other branches), esp sample code. +// TODO: Add PT-1000 support. // TODO: Improve and test fault handling, add to README test case // TODO: Enhance RtdError to differentiate between Pin and Spi (transfer) errors. -// TODO: get down to a single Error type: Use RtdError directly in private code, +// TODO: get down to a single Error type: Use RtdError directly in private code. // TODO: Enable no_std => ![cfg_attr(not(test), no_std)] -// TODO: Create an ice bath manual test program - watch temperatures go down and up -// -// Requirements for ice bath test -// 1. Explain to the user what's going to happen -// 2. Verify temperature is in the range above 40, under 110 F -// 3. Prompt user to put the probe in the ice bath -// 4. Display temperatures every second in a loop, -// which stops after 5 minutes, or when the temperature reaches -// 35 degrees F or so. -// 5. Instruct user to remove probe from the bath -// 6. Display temperatures every second in a loop, -// which stops after 5 minutes, or when the temperature reaches -// 60 degrees F or so.// FIXME: figure out what to do about this... // // TODO: Stub off hardware access by creating abstract implementations of Trait(s) and // create minimal Mock unit tests to validate basic abstract operations. @@ -257,8 +246,8 @@ pub mod rtd_reader { } /// Set calibration (ohms * 100, e.g., 40000 for 400Ω). - pub fn set_calibration(&mut self, calib: u32) { - self.inner.set_calibration(calib); + pub fn set_calibration(&mut self, calibration: u32) { + self.inner.set_calibration(calibration); } } @@ -305,13 +294,13 @@ mod private { { /// Create a new MAX31865 module (internal use only). pub fn new(spi: SPI, mut ncs: NCS) -> Result, Error> { - let default_calib = 40000; + let default_calibration = 40000; ncs.set_high().map_err(|_| Error::GpioError)?; let max31865 = Max31865 { spi, ncs, - calibration: default_calib, + calibration: default_calibration, base_config: 0, // Set in configure }; @@ -370,8 +359,8 @@ mod private { } /// Set the calibration reference resistance (internal use only). - pub fn set_calibration(&mut self, calib: u32) { - self.calibration = calib; + pub fn set_calibration(&mut self, calibration: u32) { + self.calibration = calibration; } /// Read the raw resistance value. @@ -398,7 +387,7 @@ mod private { /// The output value is the value in degrees Celsius multiplied by 100. pub fn read_default_conversion(&mut self) -> Result { let ohms = self.read_ohms()?; - let temp = super::temp_conversion::LOOKUP_VEC_PT100.lookup_temperature(ohms as i32); + let temp = temp_conversion::LOOKUP_VEC_PT100.lookup_temperature(ohms as i32); Ok(temp) } From 7474df78885ab65048f6ce5ebc6b971b8bbb188b Mon Sep 17 00:00:00 2001 From: Alan Robertson Date: Sat, 15 Nov 2025 01:15:31 -0700 Subject: [PATCH 5/5] lots of cleanups pre-publication --- Cargo.toml | 12 +++-- README.md | 95 ++++++++++++++++++++++++++++++++++----- examples/ice_bath_test.rs | 7 +-- src/lib.rs | 29 ++++++------ src/temp_conversion.rs | 10 ++--- tests/100_ohm_test.rs | 19 +++++--- 6 files changed, 123 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 69ee9d1..df826d9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "simple_max31865" -version = "1.0.0" +name = "simple-max31865" +version = "1.0.0" # Bumped for initial publish (stable API, but first release) authors = ["Alan Robertson "] keywords = ["embedded-hal-v1", "rtd-sensor", "max31865"] -categories = ["embedded", "no-std", "hardware-support"] +categories = ["embedded", "hardware-support"] description = "Easy-to-use driver for the MAX31865 RTD to Digital converter (Raspberry Pi focus)" -documentation = "https://github.com/emeric-martineau/rs-max31865" +documentation = "https://docs.rs/simple-max31865" # Standard for crates.io; will auto-generate on publish repository = "https://github.com/Alan-R/simple-max31865-rs" readme = "README.md" license = "MIT OR Apache-2.0" @@ -21,8 +21,6 @@ clap = { version = "4.0", features = ["derive"] } # For CLI args in examples [[example]] name = "ice_bath_test" path = "examples/ice_bath_test.rs" -# required-features = ["doc"] # If you want to tie it to the doc feature - [profile.test] lto = "thin" # Enables link-time optimization: Strips unused symbols (e.g., test harness bloat) like in your main binary @@ -39,4 +37,4 @@ test = true path = "src/lib.rs" [features] -doc = [] \ No newline at end of file +# Removed empty 'doc' feature; add back if needed for conditional compilation \ No newline at end of file diff --git a/README.md b/README.md index f20265c..4b5f106 100755 --- a/README.md +++ b/README.md @@ -1,25 +1,80 @@ -# `max31865` -A generic driver for the MAX31865 RTD to Digital converter +# simple-max31865 -## [Documentation](https://rudihorn.github.io/max31865/max31865/index.html) +[![Crates.io](https://img.shields.io/crates/v/simple-max31865.svg)](https://crates.io/crates/simple-max31865) +[![Docs.rs](https://docs.rs/simple-max31865/badge.svg)](https://docs.rs/simple-max31865) +[![License](https://img.shields.io/badge/license-MIT%20or%20Apache-2.0-blue.svg)](LICENSE-APACHE) -## [Examples](https://github.com/Alan-R/simple-max31865-rs/tree/updates/examples) +A Raspberry Pi driver for the MAX31865 RTD to Digital converter + +## [Documentation](https://docs.rs/simple-max31865) + +## [Examples](https://github.com/Alan-R/simple-max31865-rs/tree/main/examples) Examples are in the *examples* directory from the root of the tree. ## What works -- reading the raw value and the converted temperature value either in f64 or scaled integer forms. -- Detecting and recovering from errors -- setting the ohmic calibration value +- Reading the raw value and the converted temperature value either in f64 or scaled integer forms. +- Detecting and recovering from errors. +- Setting the ohmic calibration value. - Interaction with the chip works well. - Reading temperatures and resistances from PT-100 sensors. +- Fault detection and recovery. +- Support for Raspberry Pi (via `rppal` crate). +- Tested with Max31856 hardware from Playing With Fusion + and an Adafruit clone board, both with a PT100 sensor. ## TODO +See src/lib.rs for details. + +Future: Testing/Mocking +- Stub off hardware access by creating abstract implementations of Trait(s) and create minimal Mock unit tests (under a "mock" feature). +- Create mock implementation of SPI and Pin abstractions (controlled by a "mock" feature). +- Create "mock" hardware tests which exercise the APIs with mock feature. + +Many other thoughts listed in the `src/lib.rs` file. + +## Quick Start + +### Raspberry Pi OS Configuration + +- You need a Raspberry Pi with a working Max31865 HAT attached with a known chip select pin. +- enable GPIO/SPI via raspi-config or your OS settings before running. + +### Add to `Cargo.toml` + + [dependencies] + simple-max31865 = "1.0" + +### Basic Usage (Raspberry Pi) -- Working with the PT-1000 sensors -- Many other thoughts listed in the src/lib.rs file. +use simple_max31865::{decode_fault_status, RTDReader, RTDLeads, FilterHz}; +fn main() -> Result<(), Box> { + let mut max = RTDReader::new(24, RTDLeads::Three, FilterHz::Sixty)?; // CS pin 24, 3-wire PT100, 60Hz filter + let temperature = max.get_temperature()?; + println!("Temperature: {:.2}°C", temperature); + let resistance = max.get_resistance()?; + println!("Resistance: {:.2} Ω", resistance); + + match max.read_temp_100() { + Ok(temp) => println!("Temperature: {:.2}°C", temp as f64 / 100.0), + Err(e) => { + if max.is_max_fault(&e) { + let status = max.read_fault_status()?; + println!("Fault: {:?}", decode_fault_status(status)); // Fixed: No ? needed + let _ = max.clear_fault(); + } + return Err(e.into()); + } + } + Ok(()) +} + +## Examples + +Full examples (e.g., basic reading, fault handling) will be added to the examples/ directory soon. +The Quick Start above demonstrates core usage. ## License @@ -30,9 +85,27 @@ Licensed under either of - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. +# References + +- [MAX31865 Datasheet](https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf) +- [PT100 Sensor wiring diagrams](https://www.playingwithfusion.com/docs/1203) - a + great reference on wiring the various types of PT100 sensors. + +# Credits and License + +The simple-max31865 crate is derived from version 1.0.0 of +the [rs-max31865](https://github.com/emeric-martineau/rs-max31865) +crate by Rudi Horn and Emeric Martineau, with significant modifications: + +- Greatly simplified, opaque API hiding hardware details. +- Added Raspberry Pi support via rppal. +- Floating-point temperature and resistance reads. + +Some original code snippets (e.g., register configs) and temperature conversion etc +are reused under the MIT/Apache-2.0 license. See the original repo for their contributions. + ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any -additional terms or conditions. - +additional terms or conditions. \ No newline at end of file diff --git a/examples/ice_bath_test.rs b/examples/ice_bath_test.rs index d2ce011..b1d5164 100644 --- a/examples/ice_bath_test.rs +++ b/examples/ice_bath_test.rs @@ -10,7 +10,7 @@ // MAX31865_FILTER: String parsed to enum (e.g., 50 or Fifty). // Precedence: CLI arg > env var > default. -use simple_max31865::{decode_fault_status, FilterHz, RTDLeads, RTDReader, RtdError}; +use simple_max31865::{decode_fault_status, FilterHz, RTDLeads, RTDReader}; use std::env; use std::io; use std::time::{Duration, Instant}; @@ -141,7 +141,8 @@ fn main() -> Result<(), Box> { println!("Resistance: {:.2} ohms", resistance); println!("Temperature: {:.1}°F ({:.1}°C)", temp_f, temp_c); - if temp_f < 40.0 || temp_f > 110.0 { + if !(40.0..=110.0).contains(&temp_f) { + eprintln!("Initial temp {:.1}°F out of range (expected 40-110°F).", temp_f); eprintln!("Raw: 0x{:04X} (LSB fault: {}) | Resistance: {:.2} ohms | Temp: {:.1}°C", raw, raw & 1, resistance, temp_c); eprintln!("Likely wiring/sensor issue: Check RTD connections (expected ~108 ohms at room temp, raw ~0x6A80)."); @@ -259,7 +260,7 @@ fn main() -> Result<(), Box> { println!("Initial: {:.1}°F", initial_temp_f); println!("Bath min: {:.1}°F (expected ~32°F)", min_temp_f); println!("Recovery final: {:.1}°F (expected near initial)", final_temp_f); - if min_temp_f < 30.0 || min_temp_f > 34.0 { + if !(30.0..=34.0).contains(&min_temp_f){ println!("Potential issue: Bath temp off from 32°F. Recheck setup or compare to reference code."); } else { println!("Test passed! Sensor behaving as expected."); diff --git a/src/lib.rs b/src/lib.rs index 7da63ad..537c7be 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,9 +152,6 @@ pub fn decode_fault_status(status: u8) -> Vec<&'static str> { faults } -#[cfg(feature = "doc")] -pub mod examples; - pub const MODE: Mode = Mode { phase: Phase::CaptureOnSecondTransition, polarity: Polarity::IdleHigh, @@ -193,7 +190,7 @@ pub mod rtd_reader { .map_err(|e| RtdError::Init(format!("SPI init failed: {}", e)))?; let mut inner = Max31865::new(spi, ncs).map_err(|e| RtdError::Init(match e { - InternalError::GpioError => "NCS pin setup failed".to_string(), + InternalError::GpioFault => "NCS pin setup failed".to_string(), _ => "MAX31865 init failed".to_string(), }))?; inner.configure(leads, filter) @@ -254,10 +251,10 @@ pub mod rtd_reader { /// Map internal low-level errors to public RtdError. fn map_internal_error(e: InternalError) -> RtdError { match e { - InternalError::SpiErrorTransfer | InternalError::GpioError => { + InternalError::SpiErrorTransfer | InternalError::GpioFault => { RtdError::Read("SPI/GPIO transfer failed".to_string()) } - InternalError::MAXError => RtdError::Fault(0), // Placeholder; call read_fault_status() for real status + InternalError::MAXFault => RtdError::Fault(0), // Placeholder; call read_fault_status() for real status } } } @@ -274,10 +271,10 @@ mod private { /// Error transferring data to/from Max31865 chip registers SpiErrorTransfer, /// Error setting the state of a pin in the GPIO bus - GpioError, + GpioFault, /// The Max31865 chip declared an error when converting temperatures. /// Use `read_fault_status()` for details. - MAXError, + MAXFault, } pub struct Max31865 { @@ -296,7 +293,7 @@ mod private { pub fn new(spi: SPI, mut ncs: NCS) -> Result, Error> { let default_calibration = 40000; - ncs.set_high().map_err(|_| Error::GpioError)?; + ncs.set_high().map_err(|_| Error::GpioFault)?; let max31865 = Max31865 { spi, ncs, @@ -415,7 +412,7 @@ mod private { let retry_buffer = self.read_two(Register::RTD_MSB)?; let retry_raw = ((retry_buffer[0] as u16) << 8) | (retry_buffer[1] as u16); if retry_raw & 1 != 0 { - return Err(Error::MAXError); // Retry failed + return Err(Error::MAXFault); // Retry failed } return Ok(retry_raw); } @@ -427,11 +424,11 @@ mod private { let mut write_buffer = [0u8; 2]; write_buffer[0] = reg.read_address(); // Read addr for reg (e.g., 0x81 for 0x01) write_buffer[1] = 0; // Dummy data - self.ncs.set_low().map_err(|_| Error::GpioError)?; + self.ncs.set_low().map_err(|_| Error::GpioFault)?; self.spi .transfer(&mut read_buffer, &write_buffer) .map_err(|_| Error::SpiErrorTransfer)?; - self.ncs.set_high().map_err(|_| Error::GpioError)?; + self.ncs.set_high().map_err(|_| Error::GpioFault)?; Ok(read_buffer[1]) // Return result (ignore dummy [0]) } @@ -446,20 +443,20 @@ mod private { write_buffer[0] = reg.read_address(); // Read addr for reg (e.g., 0x81 for 0x01) write_buffer[1] = 0; // Dummy for MSB write_buffer[2] = 0; // Dummy for LSB - self.ncs.set_low().map_err(|_| Error::GpioError)?; + self.ncs.set_low().map_err(|_| Error::GpioFault)?; self.spi .transfer(&mut read_buffer, &write_buffer) .map_err(|_| Error::SpiErrorTransfer)?; - self.ncs.set_high().map_err(|_| Error::GpioError)?; + self.ncs.set_high().map_err(|_| Error::GpioFault)?; Ok([read_buffer[1], read_buffer[2]]) // Return MSB, LSB (ignore dummy [0]) } fn write(&mut self, reg: Register, val: u8) -> Result<(), Error> { - self.ncs.set_low().map_err(|_| Error::GpioError)?; + self.ncs.set_low().map_err(|_| Error::GpioFault)?; self.spi .write(&[reg.write_address(), val]) .map_err(|_| Error::SpiErrorTransfer)?; - self.ncs.set_high().map_err(|_| Error::GpioError)?; + self.ncs.set_high().map_err(|_| Error::GpioFault)?; Ok(()) } } diff --git a/src/temp_conversion.rs b/src/temp_conversion.rs index f15d81f..777f2b9 100755 --- a/src/temp_conversion.rs +++ b/src/temp_conversion.rs @@ -62,8 +62,8 @@ where } fn interpolate_index(&self, ohm_100: i32, index: usize) -> i32 { - let first = (self.reverse_index(index) as i32, self.lookup(index)); - let second = (self.reverse_index(index + 1) as i32, self.lookup(index + 1)); + let first = (self.reverse_index(index), self.lookup(index)); + let second = (self.reverse_index(index + 1), self.lookup(index + 1)); interpolate(ohm_100, first, second) } @@ -72,8 +72,8 @@ where /// # Arguments /// /// * `val` - A 16 bit unsigned integer specifying the resistance in Ohms - /// multiplied by 100, e.g. 13851 would indicate 138.51 Ohms and convert to 100 - /// degrees Celsius. + /// multiplied by 100, e.g. 13851 would indicate 138.51 Ohms and convert to 100 + /// degrees Celsius. /// /// # Remarks /// @@ -92,7 +92,7 @@ where Ok(val) => val, Err(val) => val - 1, }; - self.interpolate_index(ohm_100 as i32, index) + self.interpolate_index(ohm_100, index) } } } diff --git a/tests/100_ohm_test.rs b/tests/100_ohm_test.rs index 93ae658..ed23f36 100644 --- a/tests/100_ohm_test.rs +++ b/tests/100_ohm_test.rs @@ -1,3 +1,6 @@ +use std::env; +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +use simple_max31865::{RTDReader, RTDLeads, FilterHz}; /// The 100 Ohm resistor test. /// /// Purpose of this test - to validate basic setup without having or relying @@ -16,24 +19,26 @@ /// + + + + /// FRC+ RTD+ RTD- FRC- /// -/// Wired this way it can be configured as either a 2 or 4 wire RTD probe -/// Ideally you'll use a 1% resistor or better. -// - -use std::env; -use simple_max31865::{RTDReader, RTDLeads, FilterHz}; - +/// Wired this way it can be configured as either a 2 or 4 wire RTD probe. +/// Ideally, you'll use a 1% resistor or better. +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const RESISTOR_TOLERANCE: f64 = 0.20; //20% tolerance for generic 100Ω resistor (80–120Ω) +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const NOMINAL_OHMS: f64 = 100.0; +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const MIN_OHMS: f64 = NOMINAL_OHMS * (1.0 - RESISTOR_TOLERANCE); //80.0 +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const MAX_OHMS: f64 = NOMINAL_OHMS * (1.0 + RESISTOR_TOLERANCE); //120.0 +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] /// PT100 linear approximation: T = (R / 100 - 1) / 0.003851 (valid 0–300°C, per datasheet 5). const fn pt100_temperature_from_ohms(ohms: f64) -> f64 { (ohms / 100.0 - 1.0) / 0.003851 } +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const MIN_TEMP_C: f64 = pt100_temperature_from_ohms(MIN_OHMS); // -52.5°C +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const MAX_TEMP_C: f64 = pt100_temperature_from_ohms(MAX_OHMS); // 51.9°C fn get_pin_or_default(var_name: &str, default: u8) -> u8 {