diff --git a/Cargo.toml b/Cargo.toml index 3305424..3180ea3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,6 @@ libm = ["dep:libm", "num-traits/libm"] resistive-divider = [] thermocouple-k = ["libm"] thermistor = ["libm"] -lm35 = [] pid = [] stm32 = [] diff --git a/src/transducer/part/lm35.rs b/src/transducer/part/lm35.rs deleted file mode 100644 index 631543e..0000000 --- a/src/transducer/part/lm35.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::error::InvalidValue; -use crate::quantity::{DeciCelsius, MilliVolts, Quantity}; - -#[inline] -pub fn convert( - voltage: MilliVolts, -) -> Result, InvalidValue> { - const MIN_VOLTAGE: MilliVolts = MilliVolts(-550); - const MAX_VOLTAGE: MilliVolts = MilliVolts(1_500); - - if voltage >= MIN_VOLTAGE && voltage <= MAX_VOLTAGE { - Ok(DeciCelsius(voltage.value())) - } else { - Err(InvalidValue) - } -} diff --git a/src/transducer/part/mod.rs b/src/transducer/part/mod.rs index 0485ee0..56dbc15 100644 --- a/src/transducer/part/mod.rs +++ b/src/transducer/part/mod.rs @@ -1,10 +1,4 @@ -mod thermocouple; - -#[cfg(feature = "lm35")] -pub mod lm35; +pub mod thermocouple; #[cfg(feature = "thermistor")] -pub mod thermistor; - -#[cfg(feature = "thermocouple-k")] -pub use thermocouple::type_k as thermocouple_k; \ No newline at end of file +pub mod thermistor; \ No newline at end of file diff --git a/src/transducer/part/thermocouple.rs b/src/transducer/part/thermocouple.rs new file mode 100644 index 0000000..bb7729e --- /dev/null +++ b/src/transducer/part/thermocouple.rs @@ -0,0 +1,198 @@ +#[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)) + } +} diff --git a/src/transducer/part/thermocouple/mod.rs b/src/transducer/part/thermocouple/mod.rs deleted file mode 100644 index 108a162..0000000 --- a/src/transducer/part/thermocouple/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(feature = "thermocouple-k")] -pub mod type_k; \ No newline at end of file diff --git a/src/transducer/part/thermocouple/type_k.rs b/src/transducer/part/thermocouple/type_k.rs deleted file mode 100644 index a7e7b6c..0000000 --- a/src/transducer/part/thermocouple/type_k.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Note - Thermocouple conversion uses [f64] arithmetic internally. - -use libm::exp; -use crate::error::InvalidValue; -use crate::quantity::{Celsius, MilliVolts, Quantity}; - -fn _convert( - voltage: MilliVolts, -) -> Result, InvalidValue> { - let mv = voltage.value(); - 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; - - if mv >= -5.891 && mv <= 0.0 { - let mv_pow7 = mv_pow6 * mv; - let mv_pow8 = mv_pow7 * mv; - - let celsius = 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; - - Ok(Celsius(celsius as f32)) - } 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; - - let celsius = 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; - - Ok(Celsius(celsius as f32)) - } else if mv >= 20.644 && mv <= 54.886 { - let celsius = -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; - - Ok(Celsius(celsius as f32)) - } else { - Err(InvalidValue) - } -} - -/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and -/// directly adding the reference junction temperature to the result for offset 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. -/// -/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html). -#[inline] -pub fn convert_direct( - voltage: MilliVolts, - r_junction: Celsius, -) -> Result, InvalidValue> { - let base_temp = _convert(voltage)?; - - Ok(base_temp + r_junction) -} - -/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and -/// using a constant seebeck coefficient to correct the input voltage for offset compensation. -/// -/// Probably the right choice most of the time. -/// -/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html). -#[inline] -pub fn convert_seebeck( - voltage: MilliVolts, - r_junction: Celsius, -) -> Result, InvalidValue> { - let voltage_correction = temp_to_voltage_seebeck(r_junction)?; - _convert(MilliVolts(voltage.0 + voltage_correction.0 as f64)) -} - -/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and -/// using a polynomial to correct the input voltage for offset compensation. -/// -/// This is the most accurate method but uses the most processor cycles by a wide margin. -/// -/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html). -#[inline] -pub fn convert_polynomial( - voltage: MilliVolts, - r_junction: Celsius, -) -> Result, InvalidValue> { - let voltage_correction = temp_to_voltage_poly(r_junction)?; - _convert(MilliVolts(voltage.0 + voltage_correction.0 as f64)) -} - -pub fn temp_to_voltage_poly( - temperature: Celsius, -) -> Result, InvalidValue> { - let celsius = temperature.value(); - 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; - - if celsius >= -270.0 && celsius < 0.0 { - let cel_pow10 = cel_pow9 * celsius; - - let mv = 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; - - Ok(MilliVolts(mv as f32)) - } else if 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); - - let mv = -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; - - Ok(MilliVolts(mv as f32)) - } else { - Err(InvalidValue) - } -} - -#[inline] -pub fn temp_to_voltage_seebeck( - temperature: Celsius, -) -> Result, InvalidValue> { - if temperature.value() >= -2.0 && temperature.value() <= 800.0 { - Ok(MilliVolts(0.041 * temperature.value())) - } else { - Err(InvalidValue) - } -}