Skip to content

Commit 866c66a

Browse files
committed
significant update
Some big changes this time: 1. Added another operating mode to thermistor_interpolator. It can now take in a .csv file defining an NTC thermistor's characteristics from a bunch of temperature / resistance data points. Thermistor manufacturers commonly provide such tables. 2. Got rid of global variables except for some constants.
1 parent cca55b1 commit 866c66a

14 files changed

+1269
-285
lines changed

thermistor/NTCcalculations.cpp

Lines changed: 212 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
* resistor and optional isolation resistor monitored by an Analog to Digital
88
* Converter (ADC).
99
*
10-
* These functions use global variables declared in globals.h whose values
11-
* are expected to be set by the main() routine in thermistor_interpolator.cpp.
10+
* These functions use some of the constants defined in constants.h.
1211
*
1312
* Written in 2019 by Ben Tesch.
1413
* Originally distributed at https://github.com/slugrustle/numerical_routines
@@ -21,77 +20,264 @@
2120
*/
2221

2322
#include "NTCcalculations.h"
24-
#include "globals.h"
23+
#include "constants.h"
2524
#include <cassert>
2625
#include <cmath>
2726
#include <limits>
2827

2928
/**
3029
* Calculates nominal NTC resistance in Ohms
3130
* given an ADC reading on the range [0, ADC_counts - 1].
31+
*
32+
* ADC_counts: Total number of counts in ADC (1024 for 10-bit, 4096 for 12-bit, etc.)
33+
* Rpullup_nom_Ohms: Nominal resistance (Ω) of pullup resistor in NTC measurement circuit.
34+
* Riso_nom_Ohms: Nominal resistance (Ω) of resistor between NTC and GND.
3235
*/
33-
double Rntc_from_ADCread(const uint16_t ADCread)
36+
double Rntc_from_ADCread(const uint16_t ADCread, const uint16_t ADC_counts, const double Rpullup_nom_Ohms, const double Riso_nom_Ohms)
3437
{
3538
assert(ADCread < ADC_counts);
3639

37-
double ADCratio;
38-
if (ADCread == 0u) ADCratio = 0.5 * inv_ADC_counts_minus_one;
39-
else if (ADCread == ADC_counts - 1u) ADCratio = (static_cast<double>(ADC_counts) - 1.5) * inv_ADC_counts_minus_one;
40-
else ADCratio = static_cast<double>(ADCread) * inv_ADC_counts_minus_one;
40+
double ADCratio = 0.0;
41+
42+
if (ADCread == 0u) ADCratio = 0.5 / static_cast<double>(ADC_counts - (uint16_t)1u);
43+
else if (ADCread == ADC_counts - 1u) ADCratio = (static_cast<double>(ADC_counts) - 1.5) / static_cast<double>(ADC_counts - (uint16_t)1u);
44+
else ADCratio = static_cast<double>(ADCread) / static_cast<double>(ADC_counts - (uint16_t)1u);
45+
4146
return (Rpullup_nom_Ohms * ADCratio - Riso_nom_Ohms * (1.0 - ADCratio)) / (1.0 - ADCratio);
4247
}
4348

4449
/**
4550
* Calculates nominal NTC resistance in Ohms for a given
46-
* NTC temperature in degrees Celsius
51+
* NTC temperature in degrees Celsius.
52+
*
53+
* NTC_temp_C: NTC actual temperature (°C)
54+
* Rntc_nom_Ohms: NTC nominal resistance (Ω) at NTC_nom_temp_C (°C)
55+
* beta_K: NTC nominal ß coefficient (K)
4756
*/
48-
double Rntc_from_Tntc(double Tntc)
57+
double Rntc_from_Tntc(const double NTC_temp_C, const double Rntc_nom_Ohms, const double beta_K, const double NTC_nom_temp_C)
4958
{
50-
assert(Tntc >= -kelvin_offset);
51-
assert(std::isfinite(Tntc));
59+
assert(std::isfinite(NTC_temp_C));
60+
assert(NTC_temp_C >= -kelvin_offset);
5261

53-
return Rntc_nom_Ohms * std::exp(beta_K * (1.0 / (Tntc + kelvin_offset) - inv_NTC_nom_temp_K));
62+
return Rntc_nom_Ohms * std::exp(beta_K * (1.0 / (NTC_temp_C + kelvin_offset) - 1.0 / (NTC_nom_temp_C + kelvin_offset)));
63+
}
64+
65+
/**
66+
* Looks up NTC resistance in Ohms for a given NTC temperature
67+
* in degrees Celsius using the interpolated NTC thermistor
68+
* temperature / resistance data supplied by the user.
69+
*
70+
* data: points to the parsed NTC thermistor temperature / resistance
71+
* data that came from the user's .csv file
72+
* num_points: is the number of valid temperature / resistance data points
73+
* segments: stores the parameters for the piecewise cubic segments interpolating
74+
* the points in data
75+
*/
76+
double Rntc_from_Tntc(const double NTC_temp_C, const NTC_temp_res_row_t *data, const uint32_t num_points, const cubic_interp_seg_t *segments)
77+
{
78+
/**
79+
* Check input NTC_temp_C against table min & max temperatures.
80+
*/
81+
if (NTC_temp_C <= data[0].temp_C) return data[0].res_Ohms;
82+
if (NTC_temp_C >= data[num_points - 1u].temp_C) return data[num_points - 1u].res_Ohms;
83+
84+
/**
85+
* Find the interpolation segment that contains NTC_temp_C
86+
* via binary search.
87+
*/
88+
uint32_t lower_bound = 0u;
89+
uint32_t upper_bound = num_points - 2u;
90+
uint32_t seg_index = (lower_bound + upper_bound) >> 1;
91+
92+
while (true)
93+
{
94+
if (NTC_temp_C < data[seg_index].temp_C)
95+
{
96+
upper_bound = seg_index - 1u;
97+
seg_index = (lower_bound + upper_bound) >> 1;
98+
}
99+
else if (seg_index + 1u < num_points - 1u &&
100+
NTC_temp_C >= data[seg_index + 1u].temp_C)
101+
{
102+
lower_bound = seg_index + 1u;
103+
seg_index = (lower_bound + upper_bound) >> 1;
104+
}
105+
else
106+
{
107+
NTC_temp_res_row_t seg_row = data[seg_index];
108+
double x = NTC_temp_C - seg_row.temp_C;
109+
double x_2 = x * x;
110+
cubic_interp_seg_t seg_coeffs = segments[seg_index];
111+
return seg_coeffs.a * x * x_2 + seg_coeffs.b * x_2 + seg_coeffs.c * x + seg_row.res_Ohms;
112+
}
113+
}
54114
}
55115

56116
/**
57117
* Calculates nominal NTC temperature in degrees
58118
* Celsius given an ADC reading on the range
59119
* [0, ADC_counts - 1].
60120
* Returns NaN for infeasible ADC readings.
121+
*
122+
* ADC_counts: Total number of counts in ADC (1024 for 10-bit, 4096 for 12-bit, etc.)
123+
* Rpullup_nom_Ohms: Nominal resistance (Ω) of pullup resistor in NTC measurement circuit.
124+
* Riso_nom_Ohms: Nominal resistance (Ω) of resistor between NTC and GND.
125+
* data: points to the parsed NTC thermistor temperature / resistance
126+
* data that came from the user's .csv file
127+
* num_points: is the number of valid temperature / resistance data points
128+
* segments: stores the parameters for the piecewise cubic segments interpolating
129+
* the points in data
61130
*/
62-
double Tntc_from_ADCread(const uint16_t ADCread)
131+
double Tntc_from_ADCread(const uint16_t ADCread, const uint16_t ADC_counts, const double Rpullup_nom_Ohms,
132+
const double Riso_nom_Ohms, const double Rntc_nom_Ohms, const double beta_K, const double NTC_nom_temp_C)
63133
{
64134
assert(ADCread < ADC_counts);
65135

66-
double Rntc = Rntc_from_ADCread(ADCread);
67-
if (Rntc <= 0.0) return std::numeric_limits<double>::quiet_NaN();
68-
return 1.0 / (std::log(Rntc * inv_Rntc_nom_Ohms) * inv_beta_K + inv_NTC_nom_temp_K) - kelvin_offset;
136+
double Rntc = Rntc_from_ADCread(ADCread, ADC_counts, Rpullup_nom_Ohms, Riso_nom_Ohms);
137+
if (Rntc < min_Rntc_Ohms) return std::numeric_limits<double>::quiet_NaN();
138+
return 1.0 / (std::log(Rntc / Rntc_nom_Ohms) / beta_K + 1.0 / (NTC_nom_temp_C + kelvin_offset)) - kelvin_offset;
139+
}
140+
141+
/**
142+
* Calculates nominal NTC temperature in degrees
143+
* Celsius given an ADC reading on the range
144+
* [0, ADC_counts - 1].
145+
* Returns NaN for infeasible ADC readings.
146+
*
147+
* ADC_counts: Total number of counts in ADC (1024 for 10-bit, 4096 for 12-bit, etc.)
148+
* Rpullup_nom_Ohms: Nominal resistance (Ω) of pullup resistor in NTC measurement circuit.
149+
* Riso_nom_Ohms: Nominal resistance (Ω) of resistor between NTC and GND.
150+
* Rntc_nom_Ohms: NTC nominal resistance (Ω) at NTC_nom_temp_C (°C)
151+
* beta_K: NTC nominal ß coefficient (K)
152+
*/
153+
double Tntc_from_ADCread(const uint16_t ADCread, const uint16_t ADC_counts, const double Rpullup_nom_Ohms,
154+
const double Riso_nom_Ohms, const NTC_temp_res_row_t *data, const uint32_t num_points, const cubic_interp_seg_t *segments)
155+
{
156+
assert(ADCread < ADC_counts);
157+
158+
double Rntc = Rntc_from_ADCread(ADCread, ADC_counts, Rpullup_nom_Ohms, Riso_nom_Ohms);
159+
160+
/**
161+
* Check Rntc against table min & max resistances.
162+
*/
163+
if (Rntc >= data[0].res_Ohms) return data[0].temp_C;
164+
if (Rntc <= data[num_points - 1u].res_Ohms) return data[num_points - 1u].temp_C;
165+
166+
/**
167+
* Find the interpolation segment that contains Rntc
168+
* via binary search.
169+
*/
170+
uint32_t lower_bound = 0u;
171+
uint32_t upper_bound = num_points - 2u;
172+
uint32_t seg_index = (lower_bound + upper_bound) >> 1;
173+
174+
while (true)
175+
{
176+
if (Rntc > data[seg_index].res_Ohms)
177+
{
178+
upper_bound = seg_index - 1u;
179+
seg_index = (lower_bound + upper_bound) >> 1;
180+
}
181+
else if (seg_index + 1u < num_points - 1u &&
182+
Rntc <= data[seg_index + 1u].res_Ohms)
183+
{
184+
lower_bound = seg_index + 1u;
185+
seg_index = (lower_bound + upper_bound) >> 1;
186+
}
187+
else
188+
{
189+
/**
190+
* Use linear interpolation to get an initial estimate
191+
* for Newton's method.
192+
*/
193+
NTC_temp_res_row_t seg_row = data[seg_index];
194+
NTC_temp_res_row_t next_row = data[seg_index+1u];
195+
double guess_temp_C = seg_row.temp_C + (next_row.temp_C - seg_row.temp_C) * (seg_row.res_Ohms - Rntc) / (seg_row.res_Ohms - next_row.res_Ohms);
196+
197+
/**
198+
* Solve for the temperature that yields Rntc via Newton's method.
199+
*/
200+
cubic_interp_seg_t seg_coeffs = segments[seg_index];
201+
double x = guess_temp_C - seg_row.temp_C;
202+
double x_2 = x * x;
203+
double this_err = seg_coeffs.a * x * x_2 + seg_coeffs.b * x_2 + seg_coeffs.c * x + seg_row.res_Ohms - Rntc;
204+
205+
while (std::fabs(this_err) > 1.0e-9)
206+
{
207+
double next_temp_C = guess_temp_C - this_err / (3.0 * seg_coeffs.a * x_2 + 2.0 * seg_coeffs.b * x + seg_coeffs.c);
208+
x = next_temp_C - seg_row.temp_C;
209+
x_2 = x * x;
210+
double next_err = seg_coeffs.a * x * x_2 + seg_coeffs.b * x_2 + seg_coeffs.c * x + seg_row.res_Ohms - Rntc;
211+
212+
if (std::fabs(next_err) > std::fabs(this_err)) return std::numeric_limits<double>::quiet_NaN();
213+
214+
guess_temp_C = next_temp_C;
215+
this_err = next_err;
216+
}
217+
218+
return guess_temp_C;
219+
}
220+
}
221+
}
222+
223+
/**
224+
* Calculates nominal ADC reading for a given
225+
* NTC temperature in degrees Celsius
226+
*
227+
* Rntc_nom_Ohms: NTC nominal resistance (Ω) at NTC_nom_temp_C (°C)
228+
* beta_K: NTC nominal ß coefficient (K)
229+
* ADC_counts: Total number of counts in ADC (1024 for 10-bit, 4096 for 12-bit, etc.)
230+
* Rpullup_nom_Ohms: Nominal resistance (Ω) of pullup resistor in NTC measurement circuit.
231+
* Riso_nom_Ohms: Nominal resistance (Ω) of resistor between NTC and GND.
232+
*/
233+
uint16_t ADCread_from_Tntc(const double NTC_temp_C, const double Rntc_nom_Ohms, const double beta_K, const double NTC_nom_temp_C,
234+
const uint16_t ADC_counts, const double Rpullup_nom_Ohms, const double Riso_nom_Ohms)
235+
{
236+
assert(std::isfinite(NTC_temp_C));
237+
assert(NTC_temp_C >= -kelvin_offset);
238+
239+
double Rntc = Rntc_from_Tntc(NTC_temp_C, Rntc_nom_Ohms, beta_K, NTC_nom_temp_C);
240+
assert(Rntc >= min_Rntc_Ohms);
241+
242+
double ADCratio = (Rntc + Riso_nom_Ohms) / (Rntc + Riso_nom_Ohms + Rpullup_nom_Ohms);
243+
return static_cast<uint16_t>(std::round(ADCratio * static_cast<double>(ADC_counts - (uint16_t)1u)));
69244
}
70245

71246
/**
72247
* Calculates nominal ADC reading for a given
73248
* NTC temperature in degrees Celsius
249+
*
250+
* data: Points to the parsed NTC thermistor temperature / resistance
251+
* data that came from the user's .csv file
252+
* num_points: The number of valid temperature / resistance data points
253+
* segments: Stores the parameters for the piecewise cubic segments interpolating
254+
* the points in data
255+
* ADC_counts: Total number of counts in ADC (1024 for 10-bit, 4096 for 12-bit, etc.)
256+
* Rpullup_nom_Ohms: Nominal resistance (Ω) of pullup resistor in NTC measurement circuit.
257+
* Riso_nom_Ohms: Nominal resistance (Ω) of resistor between NTC and GND.
74258
*/
75-
uint16_t ADCread_from_Tntc(double Tntc)
259+
uint16_t ADCread_from_Tntc(const double NTC_temp_C, const NTC_temp_res_row_t *data, const uint32_t num_points,
260+
const cubic_interp_seg_t *segments, const uint16_t ADC_counts, const double Rpullup_nom_Ohms,
261+
const double Riso_nom_Ohms)
76262
{
77-
assert(Tntc >= -kelvin_offset);
78-
assert(std::isfinite(Tntc));
263+
assert(std::isfinite(NTC_temp_C));
264+
assert(NTC_temp_C >= -kelvin_offset);
79265

80-
double Rntc = Rntc_from_Tntc(Tntc);
81-
assert(Rntc >= 0.0);
266+
double Rntc = Rntc_from_Tntc(NTC_temp_C, data, num_points, segments);
267+
assert(Rntc >= min_Rntc_Ohms);
82268

83269
double ADCratio = (Rntc + Riso_nom_Ohms) / (Rntc + Riso_nom_Ohms + Rpullup_nom_Ohms);
84-
return static_cast<uint16_t>(std::round(ADCratio * static_cast<double>(ADC_counts - 1u)));
270+
return static_cast<uint16_t>(std::round(ADCratio * static_cast<double>(ADC_counts - (uint16_t)1u)));
85271
}
86272

87273
/**
88274
* Convert a floating point degrees Celsius temperature
89275
* into (1/128) degrees Celsius fixed point.
90276
*/
91-
int16_t fixed_point_C(double temp_C)
277+
int16_t fixed_point_C(const double temp_C)
92278
{
93-
assert(temp_C >= -256.0);
94-
assert(temp_C <= 255.9921875);
279+
assert(temp_C >= min_fixedpointable_temp_C);
280+
assert(temp_C <= max_fixedpointable_temp_C);
95281

96282
return static_cast<int16_t>(std::round(128.0 * temp_C));
97283
}

thermistor/NTCcalculations.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,28 @@
2020
#ifndef NTCCALCULATIONS_H_
2121
#define NTCCALCULATIONS_H_
2222

23-
#include <cinttypes>
23+
#include "types.h"
2424

25-
double Rntc_from_ADCread(const uint16_t ADCread);
25+
double Rntc_from_ADCread(const uint16_t ADCread, const uint16_t ADC_counts, const double Rpullup_nom_Ohms, const double Riso_nom_Ohms);
2626

27-
double Rntc_from_Tntc(double Tntc);
27+
double Rntc_from_Tntc(const double Tntc, const double Rntc_nom_Ohms, const double beta_K, const double NTC_nom_temp_C);
2828

29-
double Tntc_from_ADCread(const uint16_t ADCread);
29+
double Rntc_from_Tntc(const double NTC_temp_C, const NTC_temp_res_row_t *data, const uint32_t num_points, const cubic_interp_seg_t *segments);
3030

31-
uint16_t ADCread_from_Tntc(double Tntc);
31+
double Tntc_from_ADCread(const uint16_t ADCread, const uint16_t ADC_counts, const double Rpullup_nom_Ohms,
32+
const double Riso_nom_Ohms, const double Rntc_nom_Ohms, const double beta_K, const double NTC_nom_temp_C);
3233

33-
int16_t fixed_point_C(double temp_C);
34+
double Tntc_from_ADCread(const uint16_t ADCread, const uint16_t ADC_counts, const double Rpullup_nom_Ohms,
35+
const double Riso_nom_Ohms, const NTC_temp_res_row_t *data, const uint32_t num_points, const cubic_interp_seg_t *segments);
36+
37+
uint16_t ADCread_from_Tntc(const double NTC_temp_C, const double Rntc_nom_Ohms, const double beta_K, const double NTC_nom_temp_C,
38+
const uint16_t ADC_counts, const double Rpullup_nom_Ohms, const double Riso_nom_Ohms);
39+
40+
uint16_t ADCread_from_Tntc(const double NTC_temp_C, const NTC_temp_res_row_t *data, const uint32_t num_points,
41+
const cubic_interp_seg_t *segments, const uint16_t ADC_counts, const double Rpullup_nom_Ohms,
42+
const double Riso_nom_Ohms);
43+
44+
int16_t fixed_point_C(const double temp_C);
3445

3546
#endif /* #ifndef NTCCALCULATIONS_H_ */
3647

0 commit comments

Comments
 (0)