rs_max31865/
lib.rs

1//! A generic driver for the MAX31865 RTD to Digital converter
2//!
3//! # References
4//! - Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
5
6#![cfg_attr(not(test), no_std)]
7use embedded_hal::digital::{InputPin, OutputPin};
8use embedded_hal::spi::{Mode, Phase, Polarity, SpiBus};
9
10#[cfg(feature = "doc")]
11pub mod examples;
12
13pub const MODE: Mode = Mode {
14    phase: Phase::CaptureOnSecondTransition,
15    polarity: Polarity::IdleHigh,
16};
17
18pub mod temp_conversion;
19
20pub enum FilterMode {
21    Filter60Hz = 0,
22    Filter50Hz = 1,
23}
24
25pub enum SensorType {
26    TwoOrFourWire = 0,
27    ThreeWire = 1,
28}
29
30pub struct Max31865<SPI, NCS, RDY> {
31    spi: SPI,
32    ncs: NCS,
33    rdy: RDY,
34    calibration: u32,
35}
36
37#[derive(Debug)]
38pub enum Error {
39    SpiErrorRead,
40    SpiErrorWrite,
41    SpiErrorTransfert,
42    PinError,
43}
44
45impl<SPI, NCS, RDY> Max31865<SPI, NCS, RDY>
46where
47    SPI: SpiBus<u8>,
48    NCS: OutputPin,
49    RDY: InputPin,
50{
51    /// Create a new MAX31865 module.
52    ///
53    /// # Arguments
54    ///
55    /// * `spi` - The SPI module to communicate on.
56    /// * `ncs` - The chip select pin which should be set to a push pull output
57    ///           pin.
58    /// * `rdy` - The ready pin which is set low by the MAX31865 controller
59    ///           whenever it has finished converting the output.
60    ///
61    pub fn new(
62        spi: SPI,
63        mut ncs: NCS,
64        rdy: RDY,
65    ) -> Result<Max31865<SPI, NCS, RDY>, Error> {
66        let default_calib = 40000;
67
68        ncs.set_high().map_err(|_| Error::PinError)?;
69        let max31865 = Max31865 {
70            spi,
71            ncs,
72            rdy,
73            calibration: default_calib, /* value in ohms multiplied by 100 */
74        };
75
76        Ok(max31865)
77    }
78
79    /// Updates the devices configuration.
80    ///
81    /// # Arguments
82    /// * `vbias` - Set to `true` to enable V_BIAS voltage, which is required to
83    ///             correctly perform conversion.Clone
84    /// * `conversion_mode` - `true` to automatically perform conversion,
85    ///                       otherwise normally off.
86    /// * `one_shot` - Only perform detection once if set to `true`, otherwise
87    ///             repeats conversion.
88    /// * `sensor_type` - Define whether a two, three or four wire sensor is
89    ///                   used.
90    /// * `filter_mode` - Specify the mains frequency that should be used to
91    ///                   filter out noise, e.g. 50Hz in Europe.
92    ///
93    /// # Remarks
94    ///
95    /// This will update the configuration register of the MAX31865 register. If
96    /// the device doesn't properly react to this, add a delay after calling
97    /// `new` to increase the time that the chip select line is set high.
98    ///
99    /// *Note*: The correct sensor configuration also requires changes to the
100    /// PCB! Make sure to read the data sheet concerning this.
101    pub fn configure(
102        &mut self,
103        vbias: bool,
104        conversion_mode: bool,
105        one_shot: bool,
106        sensor_type: SensorType,
107        filter_mode: FilterMode,
108    ) -> Result<(), Error> {
109        let conf: u8 = ((vbias as u8) << 7)
110            | ((conversion_mode as u8) << 6)
111            | ((one_shot as u8) << 5)
112            | ((sensor_type as u8) << 4)
113            | (filter_mode as u8);
114
115        self.write(Register::CONFIG, conf)?;
116
117        Ok(())
118    }
119
120    /// Set the calibration reference resistance. This can be used to calibrate
121    /// inaccuracies of both the reference resistor and the PT100 element.
122    ///
123    /// # Arguments
124    ///
125    /// * `calib` - A 32 bit integer specifying the reference resistance in ohms
126    ///             multiplied by 100, e.g. `40000` for 400 Ohms
127    ///
128    /// # Remarks
129    ///
130    /// You can perform calibration by putting the sensor in boiling (100
131    /// degrees Celsius) water and then measuring the raw value using
132    /// `read_raw`. Calculate `calib` as `(13851 << 15) / raw >> 1`.
133    pub fn set_calibration(&mut self, calib: u32) {
134        self.calibration = calib;
135    }
136
137    /// Read the raw resistance value.
138    ///
139    /// # Remarks
140    ///
141    /// The output value is the value in Ohms multiplied by 100.
142    pub fn read_ohms(&mut self) -> Result<u32, Error> {
143        let raw = self.read_raw()?;
144        let ohms = ((raw >> 1) as u32 * self.calibration) >> 15;
145
146        Ok(ohms)
147    }
148
149    /// Read the raw resistance value and then perform conversion to degrees Celsius.
150    ///
151    /// # Remarks
152    ///
153    /// The output value is the value in degrees Celsius multiplied by 100.
154    pub fn read_default_conversion(&mut self) -> Result<i32, Error> {
155        let ohms = self.read_ohms()?;
156        let temp = temp_conversion::LOOKUP_VEC_PT100.lookup_temperature(ohms as i32);
157
158        Ok(temp)
159    }
160
161    /// Read the raw RTD value.
162    ///
163    /// # Remarks
164    ///
165    /// The raw value is the value of the combined MSB and LSB registers.
166    /// The first 15 bits specify the ohmic value in relation to the reference
167    /// resistor (i.e. 2^15 - 1 would be the exact same resistance as the reference
168    /// resistor). See manual for further information.
169    /// The last bit specifies if the conversion was successful.
170    pub fn read_raw(&mut self) -> Result<u16, Error> {
171        let msb: u16 = self.read(Register::RTD_MSB)? as u16;
172        let lsb: u16 = self.read(Register::RTD_LSB)? as u16;
173
174        Ok((msb << 8) | lsb)
175    }
176
177    /// Determine if a new conversion is available
178    ///
179    /// # Remarks
180    ///
181    /// When the module is finished converting the temperature it sets the
182    /// ready pin to low. It is automatically returned to high upon reading the
183    /// RTD registers.
184    pub fn is_ready(&mut self) -> Result<bool, RDY::Error> {
185        self.rdy.is_low()
186    }
187
188    fn read(&mut self, reg: Register) -> Result<u8, Error> {
189        let buffer: [u8; 2] = self.read_two(reg)?;
190        Ok(buffer[1])
191    }
192
193    fn read_two(&mut self, reg: Register) -> Result<[u8; 2], Error> {
194        let mut read_buffer = [0u8; 2];
195        let mut write_buffer = [0u8; 1];
196
197        write_buffer[0] = reg.read_address();
198        self.ncs.set_low().map_err(|_| Error::PinError)?;
199        self.spi
200            .transfer(&mut read_buffer, &mut write_buffer)
201            .map_err(|_| Error::SpiErrorTransfert)?;
202        self.ncs.set_high().map_err(|_| Error::PinError)?;
203
204        Ok(read_buffer)
205    }
206
207    fn write(&mut self, reg: Register, val: u8) -> Result<(), Error> {
208        self.ncs.set_low().map_err(|_| Error::PinError)?;
209        self.spi
210            .write(&[reg.write_address(), val])
211            .map_err(|_| Error::SpiErrorWrite)?;
212        self.ncs.set_high().map_err(|_| Error::PinError)?;
213        Ok(())
214    }
215}
216
217#[allow(non_camel_case_types)]
218#[allow(dead_code)]
219#[derive(Clone, Copy)]
220enum Register {
221    CONFIG = 0x00,
222    RTD_MSB = 0x01,
223    RTD_LSB = 0x02,
224    HIGH_FAULT_THRESHOLD_MSB = 0x03,
225    HIGH_FAULT_THRESHOLD_LSB = 0x04,
226    LOW_FAULT_THRESHOLD_MSB = 0x05,
227    LOW_FAULT_THRESHOLD_LSB = 0x06,
228    FAULT_STATUS = 0x07,
229}
230
231const R: u8 = 0 << 7;
232const W: u8 = 1 << 7;
233
234impl Register {
235    fn read_address(&self) -> u8 {
236        *self as u8 | R
237    }
238
239    fn write_address(&self) -> u8 {
240        *self as u8 | W
241    }
242}