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}