Welcome to mirror list, hosted at ThFree Co, Russian Federation.

Thermistor.cpp « Sensors « Heating « src - github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 873e432587709e788a867b6ebc942d4dd219376c (plain)
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*
 * Thermistor.cpp
 * Reads temperature from a thermistor or a PT1000 sensor connected to a thermistor port
 *
 *  Created on: 10 Nov 2016
 *      Author: David
 */

#include "Thermistor.h"
#include "Platform.h"
#include "RepRap.h"
#include "GCodes/GCodeBuffer/GCodeBuffer.h"

#if HAS_VREF_MONITOR
# include <GCodes/GCodes.h>
# include <Hardware/NonVolatileMemory.h>
#endif

#if SAME5x		// if using CoreN2G
# include <AnalogIn.h>
using AnalogIn::AdcBits;
#endif

// For the theory behind ADC oversampling, see http://www.atmel.com/Images/doc8003.pdf
static constexpr unsigned int AdcOversampleBits = 2;							// we use 2-bit oversampling
static constexpr int32_t OversampledAdcRange = 1u << (AdcBits + AdcOversampleBits);	// The readings we pass in should be in range 0..(AdcRange - 1)

// The Steinhart-Hart equation for thermistor resistance is:
// 1/T = A + B ln(R) + C [ln(R)]^3
//
// The simplified (beta) equation assumes C=0 and is:
// 1/T = A + (1/Beta) ln(R)
//
// The parameters that can be configured in RRF are R25 (the resistance at 25C), Beta, and optionally C.

// Create an instance with default values
Thermistor::Thermistor(unsigned int sensorNum, bool p_isPT1000) noexcept
	: SensorWithPort(sensorNum, (p_isPT1000) ? "PT1000" : "Thermistor"),
	  r25(DefaultThermistorR25), beta(DefaultThermistorBeta), shC(DefaultThermistorC), seriesR(DefaultThermistorSeriesR), adcFilterChannel(-1),
	  isPT1000(p_isPT1000), adcLowOffset(0), adcHighOffset(0)
{
	CalcDerivedParameters();
}

// Get the ADC reading
int32_t Thermistor::GetRawReading(bool& valid) const noexcept
{
	if (adcFilterChannel >= 0)
	{
		// Filtered ADC channel
		const volatile ThermistorAveragingFilter& tempFilter = reprap.GetPlatform().GetAdcFilter(adcFilterChannel);
		valid = tempFilter.IsValid();
		return tempFilter.GetSum()/(tempFilter.NumAveraged() >> AdcOversampleBits);
	}

	// Raw ADC channel
	valid = true;
	return (uint32_t)port.ReadAnalog() << AdcOversampleBits;
}

// Configure the temperature sensor
GCodeResult Thermistor::Configure(GCodeBuffer& gb, const StringRef& reply, bool& changed)
{
	if (!ConfigurePort(gb, reply, PinAccess::readAnalog, changed))
	{
		return GCodeResult::error;
	}

	if (changed)
	{
		// We changed the port, so clear the ADC corrections and set up the ADC filter if there is one
		adcLowOffset = adcHighOffset = 0;
		adcFilterChannel = reprap.GetPlatform().GetAveragingFilterIndex(port);
		if (adcFilterChannel >= 0)
		{
			reprap.GetPlatform().GetAdcFilter(adcFilterChannel).Init((1u << AdcBits) - 1);
#if HAS_VREF_MONITOR
			// Default the H and L parameters to the values from nonvolatile memory
			NonVolatileMemory mem;
			adcLowOffset = mem.GetThermistorLowCalibration(adcFilterChannel);
			adcHighOffset = mem.GetThermistorHighCalibration(adcFilterChannel);
#endif
		}
	}

	gb.TryGetFValue('R', seriesR, changed);
	if (!isPT1000)
	{
		bool seenB = false;
		gb.TryGetFValue('B', beta, seenB);
		if (seenB)
		{
			shC = 0.0;						// if user changes B and doesn't define C, assume C=0
			changed = true;
		}
		gb.TryGetFValue('C', shC, changed);
		gb.TryGetFValue('T', r25, changed);
		if (changed)
		{
			CalcDerivedParameters();
		}
	}

	if (gb.Seen('L'))
	{
		const int lVal = gb.GetIValue();
		if (lVal == 999)
		{
#if HAS_VREF_MONITOR
			bool valid;
			const int32_t val = GetRawReading(valid);
			if (valid)
			{
				const int32_t computedCorrection =
								(val - (int32_t)(reprap.GetPlatform().GetAdcFilter(VssaFilterIndex).GetSum()/(ThermistorAveragingFilter::NumAveraged() >> AdcOversampleBits)))
									/(1 << (AdcBits + AdcOversampleBits - 13));
				if (computedCorrection >= -127 && computedCorrection <= 127)
				{
					adcLowOffset = (int8_t)computedCorrection;
					reply.copy("Measured L correction for port \"");
					port.AppendPinName(reply);
					reply.catf("\" is %d", adcLowOffset);

					// Store the value in NVM
					if (!reprap.GetGCodes().IsRunningConfigFile())
					{
						NonVolatileMemory mem;
						mem.SetThermistorLowCalibration(adcFilterChannel, adcLowOffset);
						mem.EnsureWritten();
					}
				}
				else
				{
					reply.copy("Computed correction is not valid. Check that you have placed a jumper across the thermistor input.");
					return GCodeResult::error;
				}
			}
			else
			{
				reply.copy("Temperature reading is not valid");
				return GCodeResult::error;
			}
#else
			reply.copy("Thermistor input auto calibration is not supported by this hardware");
			return GCodeResult::error;
#endif
		}
		else
		{
			adcLowOffset = (int8_t)constrain<int>(lVal, std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
		}
		changed = true;
	}

	if (gb.Seen('H'))
	{
		const int hVal = gb.GetIValue();
		if (hVal == 999)
		{
#if HAS_VREF_MONITOR
			bool valid;
			const int32_t val = GetRawReading(valid);
			if (valid)
			{
				int32_t vrefReading = reprap.GetPlatform().GetAdcFilter(VrefFilterIndex).GetSum();
#ifdef DUET3
				// Duet 3 MB6HC board revisions 0.6 and 1.0 have the series resistor connected to VrefP, not VrefMon, so extrapolate the VrefMon reading to estimate VrefP.
				// Version 1.01 and later boards have the series resistors connected to VrefMon.
				if (reprap.GetPlatform().GetBoardType() == BoardType::Duet3_v06_100)
				{
					const int32_t vssaReading = reprap.GetPlatform().GetAdcFilter(VssaFilterIndex).GetSum();
					vrefReading = vssaReading + lrintf((vrefReading - vssaReading) * (4715.0/4700.0));
				}
#endif
				const int32_t computedCorrection =
								(val - (int32_t)(vrefReading/(ThermistorAveragingFilter::NumAveraged() >> AdcOversampleBits)))
									/(1 << (AdcBits + AdcOversampleBits - 13));
				if (computedCorrection >= -127 && computedCorrection <= 127)
				{
					adcHighOffset = (int8_t)computedCorrection;
					reply.copy("Measured H correction for port \"");
					port.AppendPinName(reply);
					reply.catf("\" is %d", adcHighOffset);

					// Store the value in NVM
					if (!reprap.GetGCodes().IsRunningConfigFile())
					{
						NonVolatileMemory mem;
						mem.SetThermistorHighCalibration(adcFilterChannel, adcHighOffset);
						mem.EnsureWritten();
					}
				}
				else
				{
					reply.copy("Computed correction is not valid. Check that you have disconnected the thermistor.");
					return GCodeResult::error;
				}
			}
			else
			{
				reply.copy("Temperature reading is not valid");
				return GCodeResult::error;
			}
#else
			reply.copy("Thermistor input auto calibration is not supported by this hardware");
			return GCodeResult::error;
#endif
		}
		else
		{
			adcHighOffset = (int8_t)constrain<int>(hVal, std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
		}
		changed = true;
	}

	TryConfigureSensorName(gb, changed);

	if (!changed)
	{
		CopyBasicDetails(reply);
		if (isPT1000)
		{
			// For a PT1000 sensor, only the series resistor is configurable
			reply.catf(", R:%.1f", (double)seriesR);
		}
		else
		{
			reply.catf(", T:%.1f B:%.1f C:%.2e R:%.1f", (double)r25, (double)beta, (double)shC, (double)seriesR);
		}
		reply.catf(" L:%d H:%d", adcLowOffset, adcHighOffset);

		if (reprap.Debug(moduleHeat) && adcFilterChannel >= 0)
		{
#if HAS_VREF_MONITOR
			reply.catf(", Vref %" PRIu32 " Vssa %" PRIu32 " Th %" PRIu32,
				reprap.GetPlatform().GetAdcFilter(VrefFilterIndex).GetSum(),
				reprap.GetPlatform().GetAdcFilter(VssaFilterIndex).GetSum(),
				reprap.GetPlatform().GetAdcFilter(adcFilterChannel).GetSum());
#else
			reply.catf(", Th %" PRIu32, reprap.GetPlatform().GetAdcFilter(adcFilterChannel).GetSum());
#endif
		}
	}

	return GCodeResult::ok;
}

// Get the temperature
void Thermistor::Poll() noexcept
{
	bool tempFilterValid;
	const int32_t averagedTempReading = GetRawReading(tempFilterValid);

#if HAS_VREF_MONITOR
	// Use the actual VSSA and VREF values read by the ADC
	const volatile ThermistorAveragingFilter& vrefFilter = reprap.GetPlatform().GetAdcFilter(VrefFilterIndex);
	const volatile ThermistorAveragingFilter& vssaFilter = reprap.GetPlatform().GetAdcFilter(VssaFilterIndex);
	if (tempFilterValid && vrefFilter.IsValid() && vssaFilter.IsValid())
	{
		const int32_t rawAveragedVssaReading = vssaFilter.GetSum()/(vssaFilter.NumAveraged() >> AdcOversampleBits);
		const int32_t rawAveragedVrefReading = vrefFilter.GetSum()/(vrefFilter.NumAveraged() >> AdcOversampleBits);
		const int32_t averagedVssaReading = rawAveragedVssaReading + (adcLowOffset * (1 << (AdcBits + AdcOversampleBits - 13)));
# ifdef DUET3
		// Duet 3 MB6HC board revisions 0.6 and 1.0 have the series resistor connected to VrefP, not VrefMon, so extrapolate the VrefMon reading to estimate VrefP.
		// Version 1.01 and later boards have the series resistors connected to VrefMon.
		const int32_t correctedVrefReading = (reprap.GetPlatform().GetBoardType() == BoardType::Duet3_v06_100)
											? rawAveragedVssaReading + lrintf((rawAveragedVrefReading - rawAveragedVssaReading) * (4715.0/4700.0))
											: rawAveragedVrefReading;
		const int32_t averagedVrefReading = correctedVrefReading + (adcHighOffset * (1 << (AdcBits + AdcOversampleBits - 13)));
# else
		const int32_t averagedVrefReading = rawAveragedVrefReading + (adcHighOffset * (1 << (AdcBits + AdcOversampleBits - 13)));
# endif

		// VREF is the measured voltage at VREF less the drop of a 15 ohm resistor.
		// VSSA is the voltage measured across the VSSA fuse. We assume the same maximum resistance for the fuse.
		// Assume a maximum ADC reading offset of 100.
		constexpr int32_t maxDrop = (OversampledAdcRange * VrefSeriesR)/(MinVrefLoadR + VrefSeriesR) + (100 << AdcOversampleBits);

		if (averagedVrefReading < OversampledAdcRange - maxDrop)
		{
			SetResult(TemperatureError::badVref);
		}
		else if (averagedVssaReading > maxDrop)
		{
			SetResult(TemperatureError::badVssa);
		}
		else
		{
#else
	if (tempFilterValid)
	{
		{
			const int32_t averagedVrefReading = OversampledAdcRange + (adcHighOffset * (1 << (AdcBits + AdcOversampleBits - 13)));
			const int32_t averagedVssaReading = adcLowOffset * (1 << (AdcBits + AdcOversampleBits - 13));
#endif
			// Calculate the resistance
			if (averagedVrefReading <= averagedTempReading)
			{
				SetResult((isPT1000) ? BadErrorTemperature : ABS_ZERO, TemperatureError::openCircuit);
			}
			else if (averagedTempReading <= averagedVssaReading)
			{
				SetResult(BadErrorTemperature, TemperatureError::shortCircuit);
			}
			else
			{
				float resistance = seriesR * (float)(averagedTempReading - averagedVssaReading)/(float)(averagedVrefReading - averagedTempReading);
#ifdef DUET_NG
				// The VSSA PTC fuse on the later Duets has a resistance of a few ohms. I measured 1.0 ohms on two revision 1.04 Duet WiFi boards.
				resistance -= 1.0;														// assume 1.0 ohms and only one PT1000 sensor
#endif
				if (isPT1000)
				{
					// We want 100 * the equivalent PT100 resistance, which is 10 * the actual PT1000 resistance
					const uint16_t ohmsx100 = (uint16_t)lrintf(constrain<float>(resistance * 10, 0.0, 65535.0));
					float t;
					const TemperatureError sts = GetPT100Temperature(t, ohmsx100);
					SetResult(t, sts);
				}
				else
				{
					// Else it's a thermistor
					const float logResistance = logf(resistance);
					const float recipT = shA + shB * logResistance + shC * logResistance * logResistance * logResistance;
					const float temp =  (recipT > 0.0) ? (1.0/recipT) + ABS_ZERO : BadErrorTemperature;

					// It's hard to distinguish between an open circuit and a cold high-resistance thermistor.
					// So we treat a temperature below -5C as an open circuit, unless we are using a low-resistance thermistor. The E3D thermistor has a resistance of about 470k @ -5C.
					if (temp < MinimumConnectedTemperature && resistance > seriesR * 100)
					{
						// Assume thermistor is disconnected
						SetResult(ABS_ZERO, TemperatureError::openCircuit);
					}
					else
					{
						SetResult(temp, TemperatureError::success);
					}
				}
			}
		}
	}
	else
	{
		// Filter is not ready yet
		SetResult(TemperatureError::notReady);
	}
}

// Calculate shA and shB from the other parameters
void Thermistor::CalcDerivedParameters() noexcept
{
	shB = 1.0/beta;
	const float lnR25 = logf(r25);
	shA = 1.0/(25.0 - ABS_ZERO) - shB * lnR25 - shC * lnR25 * lnR25 * lnR25;
}

// End