PaxInstruments / t400-firmware

Firmware for the Pax Instruments T400 temperature datalogger
22 stars 5 forks source link

Temperature resolution #181

Closed charlespax closed 9 years ago

charlespax commented 9 years ago

In the commit fc733ff559c312c488cb16a5921716677c6259d3 on electronics version 0.12 modified to the version 0.13 design (https://github.com/PaxInstruments/t400-electronics/issues/190) I still see the large jumps of approximately 1°C.

img_20150902_215336

For the MCP3424 at 15 samples per second and 16-bit resolution we should see approximately 0.208 °C/LSB (0.0078 mV/LSB).

Here is the code I used in Soulver to calculate values.

Vref = 2048 mV  // MCP3424 internal reference voltage
PGA = 8  // Gain multiplier
Vref_PGA = Vref/PGA  // Affective reference voltage after PGA
Res_bit = 16  // 16-bit for 15 samples per second
TC_Vmax = 54.886 mV  // K-typ max voltage
TC_Vmin = - 6.458 mV  // K-type min voltage
TC_Tmax =1370 C   // K-type temperature at TC_Vmax
TC_Tmin = - 260 C  // K-type temperature at TC_Vmin
C_per_mV = (TC_Tmax - TC_Tmin)/(TC_Vmax - TC_Vmin)  // Degrees C per mB
mV_per_LSB = (Vref_PGA × 2)/2^Res_bit  // mV per LSB, approximately
C_per_LSB = C_per_mV × mV_per_LSB  // Degrees C per LSB, approximately

I'm going to dig into the code and see what may be causing this. I'm guessing the ADC values are being truncated within the MCP2434 library. Possible taking uV to V and back to mV.

charlespax commented 9 years ago

Conclusion

The function GetTypKTemp() in functions.cpp divides an int32_t over an int32_t. This results in only integer values for the thermocouple temperature difference. The ambient temperature sensor gives decimal values. Combining these two values gives what we have observed: large temperature jumps of approximately one degree with small variations at each jump. This also explains why the displayed thermocouple temperatures always carry the same decimal number as the ambient temperature.

Check out this smoothness. img_20150903_010626

Notes

I'm working downward from the highest level...

In t400.ino #53 double temperatures[SENSOR_COUNT]; creates a table containing the values to display. A double is floating point. I see a decimal number the display, so this appears to be good.

In t400.ino #169 temperatures[channel] = convertTemperature(temperature + ambient); writes a value to the specified table cell. When Celsius units are selected #86 double convertTemperature(double Celcius){} just returns Celcius. For simplicity, I'll just look at this case. #149 double temperature; declares a floating point, so that should be good. In t400.ino #54 double ambient = 0; declares a floating point variable. Should be no problem. #151 ambient = ambientSensor.readTempC16(AMBIENT) / 16.0; This is an integer value what gets divided by sixteen. Maybe something funky happens here, but probably not because I see a decimal value for ambient temperature on the display. AMBIENT is a keyword defined in the MCP980X library keywords.txt #26 AMBIENT LITERAL1. In MCP980X.cpp #45 int MCP980X::readTempC16(MCP980X_REGS_t reg){} returns the Celsius temperature as an integer which is °C times 16.0.

So far there appears to be no problem with combining the ambient temperature and the thermocouple temperature. The issue must be with determining what the thermocouple temperature actually is. Let's take a look at temperature.

In t400.ino #163 temperature = GetTypKTemp(measuredVoltageUv); converts measuredVoltageUv to temperature using the lookup table. In functions.cpp # 316 double GetTypKTemp(int32_t microVolts) verifies microVolts is within the expected range then returns LookedupValue, which is defined by #322 double LookedupValue;.

The problem could be in functions.cpp at #330 LookedupValue = ((double)-270 + (i)*10) + ((10 *(microVolts - valueLow)) / ((valueHigh - valueLow))); where all the numbers are integers except for (double-270). The problem may be from dividing integers rather than using floating point math.

Okay, I think that's the issue. I'm not really sure how all these variable types interact, so I wrote a quick c++ program.

// Use the lookup table in typek_constant.h to determine what the
// measured temperature should be. This does not include the
// junction temperature form the MCP9800.

#include <iostream>
using namespace std;

int main()
{
  int32_t microVolts;
  int32_t valueLow = -5354;
  int32_t valueHigh = -5141;
  double LookedupValue;
  uint16_t i = 0;

   cout << "Hypothetical uV: " << endl;
   cin >> microVolts;
   cout << "Corresponding i value: " << endl;
   cin >> i;
   LookedupValue = ((double)-270 + (i)*10) + ((10 *(microVolts - valueLow)) / ((valueHigh - valueLow)));
   cout << "Calculated: " << LookedupValue << endl;
   return 0;
}

I compile and execute to get the following output.

$ clear; g++ add.cpp; chmod +x a.out ;./a.out
Hypothetical uV: 
-5200
Corresponding i value: 
10
Calculated: -163

This code gives -163 whereas a hand calculate gives -162.769953....

Now I can change the code and cast all the integers at doubles.

...
   LookedupValue = ((double)-270 + (i)*10) + 
       ((10 *((double)microVolts - (double)valueLow)) / (((double)valueHigh - (double)valueLow)));
...

This gives the appropriate LookedupValue of -162.77.

$ clear; g++ add.cpp; chmod +x a.out ;./a.out
Hypothetical uV: 
-5200
Corresponding i value: 
10
Calculated: -162.77

Maybe there is a better way to do this, but it looks like it will work. I'll test on the hardware.

Actually, I think we can just denominator as a double after the subtraction. Casting -270 as a double does not appear to do anything.

   LookedupValue = (-270 + (i)*10) + 
       ((10 *(microVolts - valueLow)) / (double)((valueHigh - valueLow)));

This gives

$ clear; g++ add.cpp; chmod +x a.out ;./a.out
Hypothetical uV: 
-5200
Corresponding i value: 
10
Calculated: -162.77

On the hardware this change works, but causes another problem. As you can see in this image we are getting much getter resolution on the readings.

img_20150902_221453

However, there appears to be some kind of rollover when the displayed thermocouple temperature drops below the ambient temperature.

img_20150902_221517

Changing the denominator from (double)((valueHigh - valueLow)) to ((double)(valueHigh - valueLow)) does not change anything

Well, it turns out that (double)-273 was really important. The only change to any code is in function.cpp 'LookedupValue = ((double)-270 + (i)10) + ((10 (microVolts - valueLow)) / ((double)(valueHigh - valueLow)));' Just cast the denominator as a double and all is well.

Check this out. img_20150903_010626