-
-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathMcp960x.cs
305 lines (273 loc) · 13.8 KB
/
Mcp960x.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Device.I2c;
using UnitsNet;
namespace Iot.Device.Mcp960x
{
/// <summary>
/// MCP960X - cold-junction compensated thermocouple to digital converter
/// </summary>
public class Mcp960x : IDisposable
{
private readonly byte _adcMeasurementResolutionType;
private readonly byte _burstModeTemperatureSamplesType;
private readonly byte _coldJunctionResolutionType;
private readonly byte _digitalFilterCoefficientsType;
private readonly byte _shutdownModesType;
private readonly byte _thermocoupleType;
private I2cDevice _i2cDevice;
/// <summary>
/// Returns absolute thermocouple temperature in Celsius
/// </summary>
/// <remarks>
/// Returns the cold-junction compensated and error-corrected thermocouple temperature in degree Celsius
/// </remarks>
public Temperature GetTemperature()
{
SpanByte data = new byte[2] { 0, 0 };
ReadBytes(Register.READ_TH, data);
return CalcTemperaturFromRegisterData(data, 0x7F);
}
/// <summary>
/// Returns the error corrected thermocouple hot junction temperature without the cold junction compensation in Celsius
/// </summary>
/// <remarks>
/// The temperatur is the error corrected thermocouple hot junction temperature without the cold junction compensation
/// </remarks>
public Temperature GetHotJunctionTemperature()
{
SpanByte data = new byte[2] { 0, 0 };
ReadBytes(Register.READ_TDELTA, data);
return CalcTemperaturFromRegisterData(data, 0x7F);
}
/// <summary>
/// Return cold junction / ambient temperature in Celsius
/// </summary>
/// <remarks>
/// The cold junction temperatur equals to the ambient temperature from the device
/// </remarks>
public Temperature GetColdJunctionTemperature()
{
SpanByte data = new byte[2] { 0, 0 };
ReadBytes(Register.READ_TC, data);
return CalcTemperaturFromRegisterData(data, 0x0F);
}
/// <summary>
/// Return device id, revision major and revision minor
/// </summary>
/// <param name="deviceID">Returns the I2C device id.</param>
/// <param name="revisionMajor">Returns the revision major.</param>
/// <param name="revisionMinor">Returns the revision minor.</param>
public void ReadDeviceID(out DeviceIDType deviceID, out byte revisionMajor, out byte revisionMinor)
{
SpanByte data = new byte[2] { 0, 0 };
ReadBytes(Register.READ_DEVICE_ID, data);
deviceID = (DeviceIDType)data[0];
revisionMajor = (byte)(data[1] >> 4);
revisionMinor = (byte)(data[1] & 0xF);
}
/// <summary>
/// Returns the status of the device
/// </summary>
/// <param name="burstComplete">Returns the burst mode conversions status flag.</param>
/// <param name="thUpdate">Returns the temperature update/conversion complete flag.</param>
/// <param name="shortCircuit">MCP9601/L01/RL01 only: Returns the short circuit detection flag.</param>
/// <param name="openCircuitOrInputRange">MCP960X/L0X/RL0X: Returns the temperature range detection flag - MCP9601/L01/RL01: Returns the open circuit detection flag.</param>
/// <param name="Alert4Status">Returns Alert 4 status bit.</param>
/// <param name="Alert3Status">Returns Alert 3 status bit.</param>
/// <param name="Alert2Status">Returns Alert 2 status bit.</param>
/// <param name="Alert1Status">Returns Alert 1 status bit.</param>
/// <remarks>
/// burstComplete: Once Burst mode is enabled, this bit is normally set after the first burst is complete. User can clear it and poll the bit periodically until the next burst of temperature conversions is complete
///
/// thUpdate: This bit is normally set. User can clear it and poll the bit until the next temperature conversion is complete.
///
/// shortCircuit:
/// MCP9601/L01/RL01 only:
/// 1 = Thermocouple Shorted to VDD or VSS
/// 0 = Normal operation
/// The VSENSE pin must be connected to the Thermocouple.
///
/// openCircuitOrInputRange:
/// MCP960X/L0X/RL0X:
/// 1 = The ADC input Voltage (EMF) or the temperature data from the TH register exceeds the measurement range for the selected thermocouple type
/// 0 = The ADC input Voltage(EMF) or the temperature data from the TH register is within the measurement range for the selected thermocouple type
/// If this bit is set, then the MCP960X/L0X/RL0X input voltage (EMF) to Degree Celsius conversion may be bypassed.
/// MCP9601/L01/RL01:
/// Indicates whether the Thermocouple is disconnected from the inputs.The VSENSE pin must be connected to the Thermocouple.
///
/// AlertXYZStatus:
/// Alert XYZ status bit
/// 1 = TX > Temperature ALERT<Y>
/// 0 = TX <= Temperature ALERT<Y>
/// Where: TX is either TH or TC
/// </remarks>
public void ReadStatus(out bool burstComplete, out bool thUpdate, out bool shortCircuit, out bool openCircuitOrInputRange,
out bool Alert4Status, out bool Alert3Status, out bool Alert2Status, out bool Alert1Status)
{
var data = ReadByte(Register.READ_WRITE_STATUS);
burstComplete = (data & 0b1000_0000) != 0;
thUpdate = (data & 0b0100_0000) != 0;
shortCircuit = (data & 0b0010_0000) != 0;
openCircuitOrInputRange = (data & 0b0001_0000) != 0;
Alert4Status = (data & 0b0000_1000) != 0;
Alert3Status = (data & 0b0000_0100) != 0;
Alert2Status = (data & 0b0000_0010) != 0;
Alert1Status = (data & 0b0000_0001) != 0;
}
/// <summary>
/// Creates a new instance of the MCP960X.
/// </summary>
/// <param name="i2cDevice">The I2C device used for communication.</param>
/// <param name="adcMeasurementResolutionType">ADC Measurement Resolution. It defaults to 18bit.</param>
/// <param name="burstModeTemperatureSamplesType">Number of Burst Mode Temperature Samples. It defaults to 1 sample.</param>
/// <param name="coldJunctionResolutionType">Cold junction resolution. It defaults to 0.0625°C.</param>
/// <param name="digitalFilterCoefficientsType">Digital filter. It defaults to MID filter.</param>
/// <param name="shutdownModesType">Shutdown Mode. It defaults to Normal operation.</param>
/// <param name="thermocoupleType">Thermocouple type. It defaults to K.</param>
/// <remarks>
/// alerts are disabled
/// </remarks>
public Mcp960x(I2cDevice i2cDevice,
ADCMeasurementResolutionType adcMeasurementResolutionType = ADCMeasurementResolutionType.R18,
BurstModeTemperatureSamplesType burstModeTemperatureSamplesType = BurstModeTemperatureSamplesType.S1,
ColdJunctionResolutionType coldJunctionResolutionType = ColdJunctionResolutionType.N_0_0625,
DigitalFilterCoefficientsType digitalFilterCoefficientsType = DigitalFilterCoefficientsType.N4,
ShutdownModesType shutdownModesType = ShutdownModesType.Normal,
ThermocoupleType thermocoupleType = ThermocoupleType.K)
{
_i2cDevice = i2cDevice ?? throw new ArgumentNullException(nameof(i2cDevice));
int deviceAddress = i2cDevice.ConnectionSettings.DeviceAddress;
if (deviceAddress < 0x60 || deviceAddress > 0x67)
{
throw new ArgumentOutOfRangeException(nameof(i2cDevice), "The MCP960X address must be between 96 (0x60) and 103 (0x67).");
}
_adcMeasurementResolutionType = (byte)adcMeasurementResolutionType;
_burstModeTemperatureSamplesType = (byte)burstModeTemperatureSamplesType;
_coldJunctionResolutionType = (byte)coldJunctionResolutionType;
_digitalFilterCoefficientsType = (byte)digitalFilterCoefficientsType;
_shutdownModesType = (byte)shutdownModesType;
_thermocoupleType = (byte)thermocoupleType;
Initialize();
}
/// <summary>
/// Standard initialization routine for Sensore Configuration Register, Device Configuration Register and Alert 1,2,3,4 Configuration Register
/// </summary>
/// <remarks>
/// alerts are disabled
/// </remarks>
private void Initialize()
{
// bit 7, 3 Unimplemented: Read as ‘0’
byte sensorRegisterValue = _thermocoupleType; // bit 6-4
sensorRegisterValue |= _digitalFilterCoefficientsType; // bit 2-0
WriteRegister(Register.READ_WRITE_CONFIGURATION_SENSOR, sensorRegisterValue);
byte deviceRegisterValue = _coldJunctionResolutionType; // bit 7
deviceRegisterValue |= _adcMeasurementResolutionType; // bit 6-5
deviceRegisterValue |= _burstModeTemperatureSamplesType; // bit 4-2
deviceRegisterValue |= _shutdownModesType; // bit 1-0
WriteRegister(Register.READ_WRITE_CONFIGURATION_DEVICE, deviceRegisterValue);
// disable alerts
WriteRegister(Register.READ_WRITE_ALERT_CONFIGURATION_1, 0x0);
WriteRegister(Register.READ_WRITE_ALERT_CONFIGURATION_2, 0x0);
WriteRegister(Register.READ_WRITE_ALERT_CONFIGURATION_3, 0x0);
WriteRegister(Register.READ_WRITE_ALERT_CONFIGURATION_4, 0x0);
}
/// <summary>
/// Returns the temperture in Celsius
/// </summary>
/// <param name="data">byte array of size 2, where UpperByte=data[0] and LowerByte=data[1].</param>
/// <param name="valueBitPattern">Bit pattern to filter out the sign bits in the UpperByte.</param>
/// <remarks>
/// Temperature >= 0°C
/// Temp = (UpperByte x 16 + LowerByte/16)
/// Temperature < 0°C
/// Temp = (UpperByte x 16 + LowerByte/16) – 4096
///
/// In case of reading register TH, TDELTA the UpperByte Bit 7 is used as sign bit -> valueBitPattern = 0x7F.
/// In case of reading register TC the UpperByte Bit 7-4 is used as sign bit -> valueBitPattern = 0x0F.
///
/// So Bit 7 of UpperByte can always be used to determin the sign of the value.
/// </remarks>
private Temperature CalcTemperaturFromRegisterData(SpanByte data, byte valueBitPattern)
{
if (data.Length != 2)
{
throw new IndexOutOfRangeException();
}
var rawValue = ((data[0] & valueBitPattern) * 16) + (data[1] * 0.0625);
// checks if the temp is negative
if ((data[0] & 0x80) == 0x80)
{
rawValue -= 4096;
}
// converts raw temperature value to struct Temperature as Degrees C
var temperatureOut = Temperature.FromDegreesCelsius(rawValue);
return temperatureOut;
}
/// <summary>
/// Writes the Data to the Spi Device
/// </summary>
/// <remarks>
/// Takes the data input byte and writes it to the spi device
/// </remarks>
/// <param name="register">Register location to write to which starts the device reading</param>
/// <param name="data">Data to write to the device</param>
private void WriteRegister(Register register, byte data)
{
SpanByte dataout = new byte[]
{
(byte)register,
data
};
_i2cDevice.Write(dataout);
}
/// <summary>
/// Writes the Data to the Spi Device
/// </summary>
/// <remarks>
/// Takes the data input byte array and writes it to the spi device
/// </remarks>
/// <param name="register">Register location to write to which starts the device reading</param>
/// <param name="data">Data to write to the device</param>
private void WriteRegister(Register register, SpanByte data)
{
SpanByte toSend = new byte[data.Length + 1];
toSend[0] = (byte)register;
data.CopyTo(toSend.Slice(1));
_i2cDevice.Write(toSend);
}
/// <summary>
/// Read of the Datas on the Device
/// </summary>
/// <remarks>
/// Writes the read address of the register and returns a byte
/// </remarks>
/// <param name="register">Register location to write to which starts the device reading</param>
private byte ReadByte(Register register)
{
_i2cDevice.WriteByte((byte)register);
return _i2cDevice.ReadByte();
}
/// <summary>
/// Read of the Datas on the Device
/// </summary>
/// <remarks>
/// Writes the read address of the register and outputs a byte list of the length provided
/// </remarks>
/// <param name="register">Register location to write to which starts the device reading</param>
/// <param name="readBytes">bytes being read</param>
private void ReadBytes(Register register, SpanByte readBytes)
{
_i2cDevice.WriteByte((byte)register);
_i2cDevice.Read(readBytes);
}
/// <inheritdoc/>
public void Dispose()
{
_i2cDevice?.Dispose();
_i2cDevice = null!;
}
}
}