pt_rtd/
lib.rs

1#![no_std]
2
3//! Calculation methods for platinum type RTD temperature sensors.
4//! 
5//! All temperature related calculations are based on DIN EN 60751:2009-05.
6//! The polynomials for PT100 and PT1000 for temperature calculation at below 0°C are from
7//! https://github.com/ulikoehler/UliEngineering/blob/master/UliEngineering/Physics/RTD.py. 
8//! 
9//! See also https://techoverflow.net/2016/01/02/accurate-calculation-of-pt100pt1000-temperature-from-resistance/
10//! for reference.
11//! 
12//! The correctional polynomials for PT200 and PT500 are not yet implemented, temperature 
13//! calculations for these below 0°C will be wrong.
14
15use libm::{
16    powf,
17    sqrtf,
18    floorf,
19};
20
21#[allow(dead_code)]
22#[non_exhaustive]
23#[derive(Clone, Copy)]
24pub enum ADCRes {
25    B8 = 255,
26    B10 = 1_023,
27    B12 = 4_095,
28    B14 = 16_383,
29    B16 = 65_535,
30    B18 = 262_143,
31    B20 = 1_048_575,
32    B22 = 4_194_303,
33    B24 = 16_777_215,
34}
35
36#[allow(dead_code)]
37#[non_exhaustive]
38#[derive(Clone, Copy)]
39pub enum RTDType {
40    PT100 = 100,
41    PT200 = 200,
42    PT500 = 500,
43    PT1000 = 1000,
44}
45
46#[allow(dead_code)]
47#[non_exhaustive]
48struct RTDCorrection;
49
50impl RTDCorrection {
51    pub const PT100: Polynomial = [1.51892983e-10, -2.85842067e-08, -5.34227299e-06,
52    1.80282972e-03, -1.61875985e-01, 4.84112370e+00];
53    pub const PT200: Polynomial = [0_f32; 6]; // FIXME: Precalculate correctional polynomial for PT200
54    pub const PT500: Polynomial = [0_f32; 6]; // FIXME: Precalculate correctional polynomial for PT500
55    pub const PT1000: Polynomial = [1.51892983e-15, -2.85842067e-12, -5.34227299e-09,
56    1.80282972e-05, -1.61875985e-02, 4.84112370e+00];
57}
58type Polynomial = [f32; 6];
59
60const A: f32 = 3.9083e-3;
61const B: f32 = -5.7750e-7;
62const C: f32 = -4.1830e-12;
63
64/// Calculate temperature of RTD from resistance value.
65/// 
66/// Allowed temperature range: -200–850°C.
67#[allow(dead_code)]
68pub fn calc_t(r: f32, r_0: RTDType) -> Result<f32, Error> {
69    let r_min = floorf(calc_r(-200_f32, r_0).unwrap()) as i32;
70    let r_max = floorf(calc_r(850_f32, r_0).unwrap()) as i32;
71
72    // set correctional polynomial for t < 0°C
73    let corr_poly: Result<[f32; 6], Error> = match r_0 {
74        RTDType::PT100 => Ok(RTDCorrection::PT100),
75        RTDType::PT200 => Ok(RTDCorrection::PT200),
76        RTDType::PT500 => Ok(RTDCorrection::PT500),
77        RTDType::PT1000 => Ok(RTDCorrection::PT1000),
78    };
79
80    // cast r_0 to f32 for calculation
81    let r_0 = r_0 as i32 as f32;
82    let mut t = ( -r_0 * A + sqrtf( powf(r_0, 2_f32) * powf(A, 2_f32) - 4_f32 * r_0 * B * ( r_0 - r as f32 ) ) ) / ( 2_f32 * r_0 as f32 * B );
83
84    match corr_poly {
85        Ok(poly) => {
86            match (floorf(r) as i32, r_0 as i32) {
87                (r, r_0) if r_0 <= r && r <= r_max => {
88                    // t >= 0°C
89                    Ok(t)
90                },
91                (r, r_0) if r_min <= r && r < r_0 => {
92                    // t < 0°C
93                    // Apply the correctional polynomial
94                    t += poly_correction(r as f32, poly);
95                    Ok(t)
96                },
97                _ => Err(Error::OutOfBounds),
98            }
99        },
100        Err(_) => Err(Error::NonexistentType),
101    }
102}
103
104/// Calculate resistance of RTD for a specified temperature.
105/// 
106/// Allowed temperature range: -200–850°C. For temperatures below 0°C a small error (58.6uK max.
107/// over the full range) is introduced due to the use of polynomial approximation.
108#[allow(dead_code)]
109pub fn calc_r(t: f32, r_0: RTDType) -> Result<f32, Error> {
110    let r_0 = r_0 as i32;
111    match floorf(t) as i32 {
112        0..=850 => Ok(r_0 as f32 * ( 1_f32 + A * t + B * powf(t, 2_f32) )),
113        -200..=-1 => Ok(r_0 as f32 * ( 1_f32 + A * t + B * powf(t, 2_f32) + C * ( t - 100_f32 ) * powf(t, 3_f32) )),
114        _ => Err(Error::OutOfBounds),
115    }
116}
117
118/// Convert digital value of relative measurement for n bit ADC to resistance.
119#[allow(dead_code)]
120pub fn conv_d_val_to_r(d_val: u32, r_ref: u32, res: ADCRes, pga_gain: u32) -> Result<f32, Error> {
121    let res = res as u32;
122    match d_val {
123        d if d <= res => Ok(d_val as f32 * r_ref as f32 / ( res as f32 * pga_gain as f32)),
124        _ => Err(Error::OutOfBounds),
125    }
126}
127
128/// Calculate polynomial correctional factor for t < 0°C.
129#[allow(dead_code)]
130fn poly_correction(r: f32, poly: Polynomial) -> f32 {
131    let mut res = 0_f32;
132    for (i, factor) in poly.iter().enumerate() {
133        res += factor * powf(r, i as f32);
134    };    
135    res
136}
137
138#[derive(Debug)]
139pub enum Error {
140    OutOfBounds,
141    NonexistentType,
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn resistance_calculation() {
150        let t = 0.0;
151        
152        let r = calc_r(t, RTDType::PT100).unwrap();
153        assert_eq!(r, 100_f32);
154    }
155
156    #[test]
157    fn temperature_calculation() {
158        let r = 100.0;
159
160        let t = calc_t(r, RTDType::PT100).unwrap();
161        assert_eq!(t, 0_f32);
162    }
163}