Skip to main content
Arduino

Return to Answer

Addendum: more errors and inconsistencies.
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

Addendum

I have noticed a few more problems and inconsistencies with your program.

Powering the thermistor from the internal reference

In setup(), you write

analogReference(INTERNAL); //use the interval votlage of 1.1V for the ADC resolution

and then, in a comment further down you have the schematic

+Vref---[Thermistor]---+--[1.8K]---GND

This suggests you are powering the voltage divider from the internal voltage reference of the MCU. You should not do that, as the datasheet states: "Note that VREF is a high impedance source, and only a capacitive load should be connected in a system."

Relationship between ADC reading and thermistor resistance

In the same comment as above, you wrote:

ADC = 1023*10000/(Rtherm+10000)

Actually, the factor is 1024, not 1023. Again, see the datasheet. And the number 10000 should be 1800, i.e. the resistance you have put between the ADC input and GND. I understand that you may have simply changed the value and forgotten to update one of the comments. But you should consider a misleading comment as a bug, especially if it's not consistent with the actual code.

For this kind of measurement, you get the best precision if the pulldown resistor has a value close to the resistance of the thermistor. The comment at the beginning of the program says your thermistor has 10 kΩ @ 25°C. This means that, if you are mostly interested in the range around 25°C, a 10 kΩ pulldown is better than 1.8 kΩ.

Relationship between array index and temperature

The comment before the LUT says:

The array index starts at zero, which corresponds to a temperature of +6^C

Then, the very first line of the LUT has the comment

//7^C to 7.9^C

And later on, the implementation of getTempFloat() assumes index 0 means 13°C. You should sort this out and make everything consistent.

BTW, your LUT values go all the way up to 1023. A reading of 1023 would mean the ADC voltage is higher than ×ばつVREF. Which in turn would imply the thermistor has a resistance lower than 14.6 Ω (assuming a 10 kΩ pulldown). I find this dubious.

Removal of the LUT

Since your program is meant to run very slowly (constDelay = 3000), you can afford doing complex computations in it. You can get the same precision with a way smaller LUT if you use a higher order interpolation. For a function sampled at constant steps, the Catmull–Rom spline is easy to implement and is way better than linear interpolation.

Or you can remove the LUT altogether and use an analytical expression instead. If you derived the LUT from such an expression, why not use it in the program? If the LUT derives from experimental data, you can try to do an empirical fit, i.e. an arbitrary function that closely fits the data. The most obvious (though maybe not the best) empirical function would be a polynomial. Below is an example of such a function that reproduces you LUT with good accuracy using a 6th degree polynomial. It assumes the first LUT entry is for a temperature of 7°C:

float getTempFloat(int pin)
{
 const float T0 = 7; // temperature at beginning of range
 int adc = analogRead(pin);
 float x = (adc-223.) / (1023.-223.) * 2 - 1; // scale to [-1:1]
 if (x < -1 || x > 1) return NAN; // out of range
 float y = 1.474 - x*(0.533 - x*(0.732 - x*0.458));
 return T0 + 26.107 + x*(19.287 - x*(3.596 - x*y));
}

Actually this may be even more accurate than your LUT-based function, as that LUT is made of integer values whereas the calibration curve should obviously be continuous.


Addendum

I have noticed a few more problems and inconsistencies with your program.

Powering the thermistor from the internal reference

In setup(), you write

analogReference(INTERNAL); //use the interval votlage of 1.1V for the ADC resolution

and then, in a comment further down you have the schematic

+Vref---[Thermistor]---+--[1.8K]---GND

This suggests you are powering the voltage divider from the internal voltage reference of the MCU. You should not do that, as the datasheet states: "Note that VREF is a high impedance source, and only a capacitive load should be connected in a system."

Relationship between ADC reading and thermistor resistance

In the same comment as above, you wrote:

ADC = 1023*10000/(Rtherm+10000)

Actually, the factor is 1024, not 1023. Again, see the datasheet. And the number 10000 should be 1800, i.e. the resistance you have put between the ADC input and GND. I understand that you may have simply changed the value and forgotten to update one of the comments. But you should consider a misleading comment as a bug, especially if it's not consistent with the actual code.

For this kind of measurement, you get the best precision if the pulldown resistor has a value close to the resistance of the thermistor. The comment at the beginning of the program says your thermistor has 10 kΩ @ 25°C. This means that, if you are mostly interested in the range around 25°C, a 10 kΩ pulldown is better than 1.8 kΩ.

Relationship between array index and temperature

The comment before the LUT says:

The array index starts at zero, which corresponds to a temperature of +6^C

Then, the very first line of the LUT has the comment

//7^C to 7.9^C

And later on, the implementation of getTempFloat() assumes index 0 means 13°C. You should sort this out and make everything consistent.

BTW, your LUT values go all the way up to 1023. A reading of 1023 would mean the ADC voltage is higher than ×ばつVREF. Which in turn would imply the thermistor has a resistance lower than 14.6 Ω (assuming a 10 kΩ pulldown). I find this dubious.

Removal of the LUT

Since your program is meant to run very slowly (constDelay = 3000), you can afford doing complex computations in it. You can get the same precision with a way smaller LUT if you use a higher order interpolation. For a function sampled at constant steps, the Catmull–Rom spline is easy to implement and is way better than linear interpolation.

Or you can remove the LUT altogether and use an analytical expression instead. If you derived the LUT from such an expression, why not use it in the program? If the LUT derives from experimental data, you can try to do an empirical fit, i.e. an arbitrary function that closely fits the data. The most obvious (though maybe not the best) empirical function would be a polynomial. Below is an example of such a function that reproduces you LUT with good accuracy using a 6th degree polynomial. It assumes the first LUT entry is for a temperature of 7°C:

float getTempFloat(int pin)
{
 const float T0 = 7; // temperature at beginning of range
 int adc = analogRead(pin);
 float x = (adc-223.) / (1023.-223.) * 2 - 1; // scale to [-1:1]
 if (x < -1 || x > 1) return NAN; // out of range
 float y = 1.474 - x*(0.533 - x*(0.732 - x*0.458));
 return T0 + 26.107 + x*(19.287 - x*(3.596 - x*y));
}

Actually this may be even more accurate than your LUT-based function, as that LUT is made of integer values whereas the calibration curve should obviously be continuous.

Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

I went through your program and found a few bugs you may want to fix. I think only one of these (wastage of RAM) is really related to your problem, but anyway, here it goes:

Fist, there are two issues with LPF(). The description states that this is a rolling average low-pass filter. This is erroneous: a rolling average would take a single reading, then report the average of the last n readings. This function, in contrast, takes n readings and reports their average. This makes a big difference: a rolling average would need to store the last n values in static memory, while your function does this for no good reason. You are just wasting 300 bytes of RAM.

Here is a reimplementation of LPF() that does the same thing as yours without wasting RAM. I changed its name to be more consistent with it's real purpose:

/*
 * Take 'count' temperature readings from 'pin' and return their average.
 * Returns NaN (not a number) if too many readings where in error.
 */
float getAvgTemp(int pin, int count)
{
 const int MAXERRORS = 5; // max number of errors that can occur
 float temp; // current temperature reading
 float tempSum = 0; // sum of temperatures
 int errCounter = 0; // number of erroneous readings
 for (int i = 0; i < count; i++) {
 // Try to get a valid reading.
 do {
 temp = getTempFloat(pin);
 if (isnan(temp))
 errCounter++;
 delay(25); // allow the ADC to settle
 } while (isnan(temp) && errCounter <= MAXERRORS);
 // Too many errors: return an error.
 if (errCounter > MAXERRORS)
 return NAN;
 tempSum += temp;
 }
 return tempSum / count;
}

Here I use NaN (not a number) as an error indicator, as it is semantically clearer than a random out-of-range value.

There are also a few errors in getTempFloat():

  • pgm_read_word(LUT_Therm[constLUTArraySize-1]) and pgm_read_word(LUT_Therm[i]) will not work: you have to pass pgm_read_word() the address where to read.
  • float(i/10) does not do what you want: it computes i/10 as an integer division (i.e. discarding the fractional part), and then it converts the result to a float. If you want a floating point division, you should make sure that at least one of the arguments is a float. The usual idiom is i/10.0.

Here is a version of getTempFloat() with these problems fixed, and also somewhat simplified:

float getTempFloat(int pin)
{
 int i, lutval, prev_lutval;
 // Take an analog reading.
 analogRead(pin); // dummy reading to settle the MUX
 delay(10);
 int adc = analogRead(pin);
 Serial.print(F("ADC: "));
 Serial.println(adc);
 // Find i such that adc lies between LUT[i-1] and LUT[i].
 for (i = 0; i < constLUTArraySize; i++) {
 prev_lutval = lutval;
 lutval = pgm_read_word(&LUT_Therm[i]);
 if (lutval >= adc) break;
 }
 // Special case: adc == LUT[0].
 if (i == 0 && adc == lutval)
 return 13.0;
 // Report an error if out of range.
 if (i == 0 || i == constLUTArraySize)
 return NAN;
 // Return interpolated temperature.
 float fraction = float(adc - prev_lutval) / (lutval - prev_lutval);
 return 13.0 + (i-1 + fraction) / 10.0;
}
lang-cpp

AltStyle によって変換されたページ (->オリジナル) /