Skip to content

Commit cc6cc7f

Browse files
authored
Adding Max31865 (#44)
1 parent 7366d14 commit cc6cc7f

23 files changed

+842
-0
lines changed

devices/Max31865/Configuration.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Iot.Device.Max31865
7+
{
8+
[Flags]
9+
internal enum Configuration : byte
10+
{
11+
Filter60HZ = 0b_0000_0000,
12+
Filter50HZ = 0b_0000_0001,
13+
FaultStatus = 0b_0000_0010,
14+
TwoFourWire = 0b_0000_0000,
15+
ThreeWire = 0b_0001_0000,
16+
OneShot = 0b_0010_0000,
17+
ConversionModeAuto = 0b_0100_0000,
18+
Bias = 0b_1000_0000
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Iot.Device.Max31865
5+
{
6+
/// <summary>
7+
/// Notch frequencies for the noise rejection filter
8+
/// </summary>
9+
public enum ConversionFilterMode : byte
10+
{
11+
/// <summary>
12+
/// Reject 50Hz and its harmonics
13+
/// </summary>
14+
Filter50Hz = 65,
15+
16+
/// <summary>
17+
/// Reject 60Hz and its harmonics
18+
/// </summary>
19+
Filter60Hz = 55
20+
}
21+
}

devices/Max31865/FaultStatus.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#pragma warning disable CS1591, CS1572, CS1573
5+
6+
namespace Iot.Device.Max31865
7+
{
8+
/// <summary>
9+
/// MAX31865 Fault Status
10+
/// </summary>
11+
12+
public class FaultStatus
13+
{
14+
/// <summary>
15+
/// FaultStatus Constructor
16+
/// </summary>
17+
/// <param name="overUnderVoltage">If an overvoltage or undervoltage has occurred.</param>
18+
/// <param name="resistanceTemperatureDetectorLow">Resistance temperature detector is low.</param>
19+
/// <param name="referenceInLow">Reference in is low.</param>
20+
/// <param name="referenceInHigh">Reference in is high.</param>
21+
/// <param name="lowThreshold">The ADC conversion is less than or equal to the low threshold.</param>
22+
/// <param name="highThreshold">The ADC conversion is greater than or equal to the high threshold.</param>
23+
public FaultStatus(bool overUnderVoltage, bool resistanceTemperatureDetectorLow, bool referenceInLow, bool referenceInHigh, bool lowThreshold, bool highThreshold)
24+
{
25+
OverUnderVoltage = overUnderVoltage;
26+
ResistanceTemperatureDetectorLow = resistanceTemperatureDetectorLow;
27+
ReferenceInLow = referenceInLow;
28+
ReferenceInHigh = referenceInHigh;
29+
LowThreshold = lowThreshold;
30+
HighThreshold = highThreshold;
31+
}
32+
33+
/// <summary>
34+
/// If an overvoltage or undervoltage has occurred.
35+
/// </summary>
36+
public bool OverUnderVoltage { get; set; }
37+
38+
/// <summary>
39+
/// Resistance temperature detector is low.
40+
/// </summary>
41+
public bool ResistanceTemperatureDetectorLow { get; set; }
42+
43+
/// <summary>
44+
/// Reference in is low.
45+
/// </summary>
46+
public bool ReferenceInLow { get; set; }
47+
48+
/// <summary>
49+
/// Reference in is high.
50+
/// </summary>
51+
public bool ReferenceInHigh { get; set; }
52+
53+
/// <summary>
54+
/// The ADC conversion is less than or equal to the low threshold.
55+
/// </summary>
56+
public bool LowThreshold { get; set; }
57+
58+
/// <summary>
59+
/// The ADC conversion is greater than or equal to the high threshold.
60+
/// </summary>
61+
public bool HighThreshold { get; set; }
62+
}
63+
}

devices/Max31865/Max31865.cs

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers.Binary;
6+
using System.Device.Spi;
7+
using System.Threading;
8+
using UnitsNet;
9+
10+
namespace Iot.Device.Max31865
11+
{
12+
/// <summary>
13+
/// MAX31865 Resistance Temperature Detector to Digital Converter
14+
/// </summary>
15+
/// <remarks>
16+
/// Documentation https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
17+
/// </remarks>
18+
public class Max31865 : IDisposable
19+
{
20+
// Callender Van Dusen coefficiants A/B for temperature conversion
21+
private const double TemperatureCoefficiantA = 3.9083e-3;
22+
private const double TemperatureCoefficiantB = -5.775e-7;
23+
24+
private readonly PlatinumResistanceThermometerType _prtType;
25+
private readonly ResistanceTemperatureDetectorWires _rtdWires;
26+
private readonly ConversionFilterMode _filterMode;
27+
private readonly double _referenceResistor;
28+
private readonly bool _shouldDispose;
29+
private SpiDevice _spiDevice;
30+
31+
/// <summary>
32+
/// MAX31865 Spi Clock Frequency
33+
/// </summary>
34+
public const int SpiClockFrequency = 5_000_000;
35+
36+
/// <summary>
37+
/// MAX31865 SPI Mode 1
38+
/// </summary>
39+
public const SpiMode SpiMode1 = SpiMode.Mode1;
40+
41+
/// <summary>
42+
/// MAX31865 SPI Mode 3
43+
/// </summary>
44+
public const SpiMode SpiMode3 = SpiMode.Mode3;
45+
46+
/// <summary>
47+
/// MAX31865 SPI Data Flow
48+
/// </summary>
49+
public const DataFlow SpiDataFlow = DataFlow.MsbFirst;
50+
51+
/// <summary>
52+
/// MAX31865 Temperature
53+
/// </summary>
54+
public Temperature Temperature { get => Temperature.FromDegreesCelsius(GetTemperature()); }
55+
56+
/// <summary>
57+
/// Creates a new instance of the MAX31865.
58+
/// </summary>
59+
/// <param name="spiDevice">The communications channel to a device on a SPI bus</param>
60+
/// <param name="platinumResistanceThermometerType">The type of Platinum Resistance Thermometer</param>
61+
/// <param name="resistanceTemperatureDetectorWires">The number of wires the Platinum Resistance Thermometer has</param>
62+
/// <param name="referenceResistor">The reference resistor value in Ohms.</param>
63+
/// <param name="filterMode">Noise rejection filter mode</param>
64+
/// <param name="shouldDispose">True to dispose the SPI device</param>
65+
public Max31865(SpiDevice spiDevice, PlatinumResistanceThermometerType platinumResistanceThermometerType, ResistanceTemperatureDetectorWires resistanceTemperatureDetectorWires, ElectricResistance referenceResistor, ConversionFilterMode filterMode = ConversionFilterMode.Filter60Hz, bool shouldDispose = true)
66+
{
67+
_spiDevice = spiDevice ?? throw new ArgumentNullException(nameof(spiDevice));
68+
_prtType = platinumResistanceThermometerType;
69+
_rtdWires = resistanceTemperatureDetectorWires;
70+
_filterMode = filterMode;
71+
_referenceResistor = referenceResistor.Ohms;
72+
_shouldDispose = shouldDispose;
73+
Initialize();
74+
}
75+
76+
/// <inheritdoc/>
77+
public void Dispose()
78+
{
79+
if (_shouldDispose)
80+
{
81+
_spiDevice?.Dispose();
82+
}
83+
84+
_spiDevice = null!;
85+
}
86+
87+
/// <summary>
88+
/// The fault state of the sensor
89+
/// </summary>
90+
public FaultStatus Faults
91+
{
92+
get
93+
{
94+
SpanByte readBuffer = new byte[2];
95+
WriteRead(Register.FaultStatus, readBuffer);
96+
97+
return new FaultStatus(
98+
(readBuffer[1] & 0x04) == 0x04,
99+
(readBuffer[1] & 0x08) == 0x08,
100+
(readBuffer[1] & 0x20) == 0x20,
101+
(readBuffer[1] & 0x10) == 0x10,
102+
(readBuffer[1] & 0x40) == 0x40,
103+
(readBuffer[1] & 0x80) == 0x80);
104+
}
105+
}
106+
107+
/// <summary>
108+
/// Standard initialization routine.
109+
/// </summary>
110+
/// <remarks>
111+
/// You can add new write lines if you want to alter the settings of the device. Settings can be found in the Technical Manual
112+
/// </remarks>
113+
private void Initialize()
114+
{
115+
SpanByte configurationSetting = new byte[]
116+
{
117+
(byte)Register.ConfigurationWrite,
118+
(byte)((byte)(_rtdWires == ResistanceTemperatureDetectorWires.ThreeWire ? Configuration.ThreeWire : Configuration.TwoFourWire) | (byte)(_filterMode == ConversionFilterMode.Filter50Hz ? Configuration.Filter50HZ : Configuration.Filter60HZ))
119+
};
120+
121+
Write(configurationSetting);
122+
}
123+
124+
/// <summary>
125+
/// Clears all the faults
126+
/// </summary>
127+
private void ClearFaults()
128+
{
129+
SpanByte configuration = new byte[2];
130+
WriteRead(Register.ConfigurationRead, configuration);
131+
132+
// Page 14 (Fault Status Clear (D1)) of the technical documentation
133+
configuration[1] = (byte)(configuration[1] & ~0x2C);
134+
configuration[1] |= (byte)Configuration.FaultStatus;
135+
136+
configuration[0] = (byte)Register.ConfigurationWrite;
137+
Write(configuration);
138+
}
139+
140+
/// <summary>
141+
/// Enable/Disable the bias voltage on the resistance temperature detector sensor
142+
/// </summary>
143+
private void EnableBias(bool enable)
144+
{
145+
SpanByte configuration = new byte[2];
146+
WriteRead(Register.ConfigurationRead, configuration);
147+
148+
configuration[1] = enable ? (byte)(configuration[1] | (byte)Configuration.Bias) : (byte)(configuration[1] & ~(byte)Configuration.Bias);
149+
150+
configuration[0] = (byte)Register.ConfigurationWrite;
151+
Write(configuration);
152+
}
153+
154+
/// <summary>
155+
/// Enable/Disable the one shot mode on the resistance temperature detector sensor
156+
/// </summary>
157+
private void EnableOneShot(bool enable)
158+
{
159+
SpanByte configuration = new byte[2];
160+
WriteRead(Register.ConfigurationRead, configuration);
161+
162+
configuration[1] = enable ? (byte)(configuration[1] | (byte)Configuration.OneShot) : (byte)(configuration[1] & ~(byte)Configuration.OneShot);
163+
164+
configuration[0] = (byte)Register.ConfigurationWrite;
165+
Write(configuration);
166+
}
167+
168+
/// <summary>
169+
/// Reads the raw resistance temperature detector value and converts it to a temperature using the Callender Van Dusen temperature conversion of 15 bit ADC resistance ratio data.
170+
/// </summary>
171+
/// <remarks>
172+
/// This math originates from: http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf
173+
/// </remarks>
174+
/// <returns>Temperature in degrees celsius</returns>
175+
private double GetTemperature()
176+
{
177+
short rtdNominal = (short)_prtType;
178+
double z1, z2, z3, z4;
179+
double temperature;
180+
181+
double resistance = GetResistance();
182+
183+
z1 = -TemperatureCoefficiantA;
184+
z2 = TemperatureCoefficiantA * TemperatureCoefficiantA - (4 * TemperatureCoefficiantB);
185+
z3 = (4 * TemperatureCoefficiantB) / rtdNominal;
186+
z4 = 2 * TemperatureCoefficiantB;
187+
188+
temperature = z2 + (z3 * resistance);
189+
temperature = (Math.Sqrt(temperature) + z1) / z4;
190+
191+
if (temperature < 0)
192+
{
193+
// For the following math to work, nominal resistance temperature detector resistance must be normalized to 100 ohms
194+
resistance /= rtdNominal;
195+
resistance *= 100;
196+
197+
double rpoly = resistance;
198+
199+
temperature = -242.02;
200+
temperature += 2.2228 * rpoly;
201+
rpoly *= resistance; // square
202+
temperature += 2.5859e-3 * rpoly;
203+
rpoly *= resistance; // ^3
204+
temperature -= 4.8260e-6 * rpoly;
205+
rpoly *= resistance; // ^4
206+
temperature -= 2.8183e-8 * rpoly;
207+
rpoly *= resistance; // ^5
208+
temperature += 1.5243e-10 * rpoly;
209+
}
210+
211+
return temperature;
212+
}
213+
214+
/// <summary>
215+
/// Read the resistance of the resistance temperature detector.
216+
/// </summary>
217+
/// <returns>Resistance in Ohms</returns>
218+
private double GetResistance()
219+
{
220+
double rtd = ReadRawRTD();
221+
rtd /= 32768;
222+
rtd *= _referenceResistor;
223+
224+
return rtd;
225+
}
226+
227+
/// <summary>
228+
/// Read the raw 16-bit value from the resistance temperature detector reistance registers in one shot mode
229+
/// </summary>
230+
/// <returns>The raw 16-bit value, NOT temperature</returns>
231+
private ushort ReadRawRTD()
232+
{
233+
ClearFaults();
234+
EnableBias(true);
235+
236+
// Page 3 (Bias Voltage Startup Time) & 4 (see note 4) of the technical documentation
237+
Thread.Sleep(10);
238+
239+
EnableOneShot(true);
240+
241+
// Page 3 (Temperature Conversion Time) of the technical documentation
242+
Thread.Sleep((short)_filterMode);
243+
244+
SpanByte readBuffer = new byte[3];
245+
WriteRead(Register.RTDMSB, readBuffer);
246+
247+
EnableBias(false); // Disable Bias current again to reduce selfheating.
248+
249+
return (ushort)(BinaryPrimitives.ReadUInt16BigEndian(readBuffer.Slice(1, 2)) >> 1);
250+
}
251+
252+
/// <summary>
253+
/// Writes the Data to the Spi Device
254+
/// </summary>
255+
/// <remarks>
256+
/// Takes the data input byte and writes it to the spi device
257+
/// </remarks>
258+
/// <param name="data">Data to write to the device</param>
259+
private void Write(SpanByte data) => _spiDevice.Write(data);
260+
261+
/// <summary>
262+
/// Full Duplex Read of the Data on the Spi Device
263+
/// </summary>
264+
/// <remarks>
265+
/// Writes the read address of the register and outputs a byte list of the length provided
266+
/// </remarks>
267+
/// <param name="register">Register location to write to which starts the device reading</param>
268+
/// <param name="readBuffer">Number of bytes being read</param>
269+
private void WriteRead(Register register, SpanByte readBuffer)
270+
{
271+
SpanByte regAddrBuf = new byte[readBuffer.Length];
272+
273+
regAddrBuf[0] = (byte)(register);
274+
_spiDevice.TransferFullDuplex(regAddrBuf, readBuffer);
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)