#[cfg(feature = "thermocouple-k")] pub mod k { //! Type K thermocouple conversion using [f64] arithmetic internally. //! //! All conversion functions clamp their inputs to the valid range rather than returning errors. //! Use the `MIN_*` / `MAX_*` constants to check whether an input is in range before //! calling a conversion function if out-of-range detection is needed. use libm::exp; use crate::quantity::{Celsius, MilliVolts}; // ----- Voltage-to-temperature constants ----- /// Minimum voltage accepted by the NIST inverse polynomial (-5.891 mV ≈ -210 °C). pub const MIN_VOLTAGE: MilliVolts = MilliVolts(-5.891); /// Maximum voltage accepted by the NIST inverse polynomial (54.886 mV ≈ 1372 °C). pub const MAX_VOLTAGE: MilliVolts = MilliVolts(54.886); // ----- Temperature-to-voltage constants ----- /// Minimum temperature accepted by the NIST forward polynomial (-270 °C). pub const MIN_TEMP_POLY: Celsius = Celsius(-270.0); /// Maximum temperature accepted by the NIST forward polynomial (1372 °C). pub const MAX_TEMP_POLY: Celsius = Celsius(1372.0); /// Minimum temperature accepted by the Seebeck approximation (-2 °C). pub const MIN_TEMP_SEEBECK: Celsius = Celsius(-2.0); /// Maximum temperature accepted by the Seebeck approximation (800 °C). pub const MAX_TEMP_SEEBECK: Celsius = Celsius(800.0); // ----- Voltage to temperature (inverse polynomial) ----- /// Core NIST ITS-90 inverse polynomial for type K. /// Input is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`]. fn voltage_to_temp(voltage: MilliVolts) -> Celsius { let mv = voltage.0.clamp(MIN_VOLTAGE.0, MAX_VOLTAGE.0); let mv_pow2 = mv * mv; let mv_pow3 = mv_pow2 * mv; let mv_pow4 = mv_pow3 * mv; let mv_pow5 = mv_pow4 * mv; let mv_pow6 = mv_pow5 * mv; let celsius = if mv >= -5.891 && mv <= 0.0 { let mv_pow7 = mv_pow6 * mv; let mv_pow8 = mv_pow7 * mv; 2.5173462E+1 * mv + -1.1662878 * mv_pow2 + -1.0833638 * mv_pow3 + -8.9773540E-1 * mv_pow4 + -3.7342377E-1 * mv_pow5 + -8.6632643E-2 * mv_pow6 + -1.0450598E-2 * mv_pow7 + -5.1920577E-4 * mv_pow8 } else if mv > 0.0 && mv < 20.644 { let mv_pow7 = mv_pow6 * mv; let mv_pow8 = mv_pow7 * mv; let mv_pow9 = mv_pow8 * mv; 2.508355E+1 * mv + 7.860106E-2 * mv_pow2 + -2.503131E-1 * mv_pow3 + 8.315270E-2 * mv_pow4 + -1.228034E-2 * mv_pow5 + 9.804036E-4 * mv_pow6 + -4.413030E-5 * mv_pow7 + 1.057734E-6 * mv_pow8 + -1.052755E-8 * mv_pow9 } else { // mv >= 20.644 && mv <= 54.886 -1.318058e2 + 4.830222E+1 * mv + -1.646031 * mv_pow2 + 5.464731E-2 * mv_pow3 + -9.650715E-4 * mv_pow4 + 8.802193E-6 * mv_pow5 + -3.110810E-8 * mv_pow6 }; Celsius(celsius as f32) } // ----- Public voltage-to-temperature conversions ----- /// Convert thermocouple voltage to temperature by directly adding the reference junction /// temperature to the polynomial result for cold-junction compensation. /// /// Can be useful compared to [`convert_seebeck`] when the reference temperature or the /// temperature being read by the thermocouple is fairly close to 0 °C. /// /// Voltage is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`]. /// /// Uses the [NIST type K inverse polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html). #[inline] pub fn convert_direct( voltage: MilliVolts, r_junction: Celsius, ) -> Celsius { voltage_to_temp(voltage) + r_junction } /// Convert thermocouple voltage to temperature using a constant Seebeck coefficient to /// correct the input voltage for cold-junction compensation. /// /// Probably the right choice most of the time. /// /// Voltage is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`]. /// Reference junction temperature is clamped to [`MIN_TEMP_SEEBECK`]..=[`MAX_TEMP_SEEBECK`]. /// /// Uses the [NIST type K inverse polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html). #[inline] pub fn convert_seebeck( voltage: MilliVolts, r_junction: Celsius, ) -> Celsius { let voltage_correction = temp_to_voltage_seebeck(r_junction); voltage_to_temp(MilliVolts(voltage.0 + voltage_correction.0 as f64)) } /// Convert thermocouple voltage to temperature using the full NIST forward polynomial to /// correct the input voltage for cold-junction compensation. /// /// This is the most accurate method but uses the most processor cycles by a wide margin. /// /// Voltage is clamped to [`MIN_VOLTAGE`]..=[`MAX_VOLTAGE`]. /// Reference junction temperature is clamped to [`MIN_TEMP_POLY`]..=[`MAX_TEMP_POLY`]. /// /// Uses the [NIST type K inverse polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html). #[inline] pub fn convert_polynomial( voltage: MilliVolts, r_junction: Celsius, ) -> Celsius { let voltage_correction = temp_to_voltage_poly(r_junction); voltage_to_temp(MilliVolts(voltage.0 + voltage_correction.0 as f64)) } // ----- Temperature to voltage (forward functions) ----- /// Convert a temperature to a type K thermocouple voltage using the full NIST forward /// polynomial. /// /// Temperature is clamped to [`MIN_TEMP_POLY`]..=[`MAX_TEMP_POLY`]. pub fn temp_to_voltage_poly(temperature: Celsius) -> MilliVolts { let celsius = temperature.0.clamp(MIN_TEMP_POLY.0, MAX_TEMP_POLY.0); let cel_pow2 = celsius * celsius; let cel_pow3 = cel_pow2 * celsius; let cel_pow4 = cel_pow3 * celsius; let cel_pow5 = cel_pow4 * celsius; let cel_pow6 = cel_pow5 * celsius; let cel_pow7 = cel_pow6 * celsius; let cel_pow8 = cel_pow7 * celsius; let cel_pow9 = cel_pow8 * celsius; let mv = if celsius >= -270.0 && celsius < 0.0 { let cel_pow10 = cel_pow9 * celsius; 0.394501280250E-01 * celsius + 0.236223735980E-04 * cel_pow2 + -0.328589067840E-06 * cel_pow3 + -0.499048287770E-08 * cel_pow4 + -0.675090591730E-10 * cel_pow5 + -0.574103274280E-12 * cel_pow6 + -0.310888728940E-14 * cel_pow7 + -0.104516093650E-16 * cel_pow8 + -0.198892668780E-19 * cel_pow9 + -0.163226974860E-22 * cel_pow10 } else { // celsius >= 0.0 && celsius <= 1372.0 let base = celsius - 0.126968600000E+03; let exponent = -0.118343200000E-03 * (base * base); let addition = 0.1185976 * exp(exponent); -0.176004136860E-01 + 0.389212049750E-01 * celsius + 0.185587700320E-04 * cel_pow2 + -0.994575928740E-07 * cel_pow3 + 0.318409457190E-09 * cel_pow4 + -0.560728448890E-12 * cel_pow5 + 0.560750590590E-15 * cel_pow6 + -0.320207200030E-18 * cel_pow7 + 0.971511471520E-22 * cel_pow8 + -0.121047212750E-25 * cel_pow9 + addition }; MilliVolts(mv as f32) } /// Convert a temperature to a type K thermocouple voltage using a constant Seebeck /// coefficient approximation (41 µV/°C). /// /// Temperature is clamped to [`MIN_TEMP_SEEBECK`]..=[`MAX_TEMP_SEEBECK`]. #[inline] pub fn temp_to_voltage_seebeck(temperature: Celsius) -> MilliVolts { MilliVolts(0.041 * temperature.0.clamp(MIN_TEMP_SEEBECK.0, MAX_TEMP_SEEBECK.0)) } }