Tag Archives: sensors

Normalizing a Large Group of Temperature Sensors (with a Sous Vide Cooler)

In this post we will describe a process of adjusting raw data from multiple temperature sensors to remove offsets for accurate comparison when they are deployed as a set. This is not the same as calibration which often uses references with exact physical values to improve accuracy. However, normalization is a highly complementary way to improve precision which can be vital when you are studying systems that are driven primarily by gradients. Sous-vide cookers are portable so this procedure can be done in the field with a mix of sensors from different vendors. Sensors that are not inherently waterproof can be sealed inside food vacuum bags during the procedure.

The Anova Sous Vide Cooker slides perfectly through the indents in the lid of this Coleman cooler although a simple styrofoam container could also be used. This combination provides vigorous water circulation and thermal control on par with most benchtop water baths.
A PETG base plate was created in Tinkercad to maintain space between the loggers for water flow.
M10x30mm stainless steel washers on the bottom of the plate provide about 10 grams of ballast each.

Here we used this rig to normalize as set of Si7051’s reference sensors that will later be for calibrating the NTC thermistors on every Cave Pearl logger. We have accumulated quite a few of those Si’s over the years and it will be interesting to see if the older ones have drifted. But this 48 quart cooler also has enough volume to enable normalization of the temperature sensors inside the larger PVC housings on our older generation of loggers.

Forty loggers immersed in 16 litres of water with sufficient clearance for the circulator.

With the significant setup time, I created several ramps to provide some surplus data. Even with the thermal control of the Anova, I prefer to use the slow passive cooling curves whenever possible. With the circulator operating continuously the system took five days to cool from 40 to 20°C: slow enough to avoid any lag issues due to the housings or inconsistencies in the circulation.

Linear Y=Mx+B Normalization:

We have released many normalization tutorials over the years, but I will review the basic steps here for completeness. You start by drawing an average from your group of sensors. This will be the value each individual sensor gets adjusted to: (click to enlarge the images)

1. Create a calculation column with the average of the ‘raw’ temp. readings from all sensors in the group

With that you calculate normalization constants using Excel’s slope & intercept functions with the columns of average & raw data for each sensor. Using these formulas lets you copy/paste the equations from one data set to the next which dramatically speeds up the process when you are working through many sensors. It also recalculates those constants if you add or delete information from the normalization set:

2. The slope.
3. The Intercept

The next step is to calculate the difference (called residuals) between an individual sensors output and the average: you will do this both before and after the Y=Mx+B corrections have been applied to the temperature readings. These differences between the group average and a given sensor should be dramatically reduced by the adjustment:

4. Calculate the ‘raw’ residuals
5. Apply the Y=Mx+B normalization
6. Calculate the ‘normalized’ residuals

Then create scatter plots of the pre & post normalization residuals so you can compare them side-by-side. The adjusted residuals should have very little slope and be equally distributed around ZERO:

7. Compare the pre & post normalization residual plots for EVERY sensor. If an individual sensor has problems it will cause the residual plots for every other sensor to exhibit structured patterns. If those patterns disappear when that sensor is eliminated from the average column then that sensor may be defective.

I should mention here how remarkable it was that this batch of sensors produced what I can only describe as textbook residuals with soft fuzzy distributions showing no observable patterns. Usually a batch of mixed sensors will show some structures in the residual plots – indicating that one (or more) of the sensors in the group has some kind of issue and might need to be removed from the group average. A failing sensor or connection issue should be your first suspect if you see step-changes in the residual plots when you know the physical system was changing slowly.

A common source of problems during normalization is accidentally including data from a logger that has its timestamp offset from the rest of the group. This can be due to human error during setup but it also creeps in due to clock drift. In that case the overall pattern in the residuals plots will be a version of the same shapes you see in the raw sensor data. Its often useful to have some deliberately induced ‘sharp’ temperature changes in the raw data record to align the raw data records.

Sensors with heavy physical housings will tend to have excessive thermal lag compared to others a mixed normalization group. In that case you need to use the sous-vide cooker to create thermal plateaus that last long enough for all sensors to equalize – and only include that stabilized data in the normalization. The Anova itself takes a hour to settle near ambient, so you will need to leave the system for several hours at each temperature if you use the stepped plateau method. And it’s not unusual for a sensor inside an underwater housing to have more than an hour of thermal lag.

The Anova overshoots near room temp. but this is far outside the cookers intended range.

The result:

The Si7051 sensors we were using here have a maximum accuracy of ±0.1 °C in the human body temperature range of 35.8 °C to 41 °C, but this widens to ±0.25 °C between -40 and +125 °C. Even with this batch starting within that spec our normalization tightens the band to about 0.05°C :

Like calibration, the raw data used to normalize sensors should cover the same range you expect to monitor in the field. There is no guarantee that fitting your sensors to an average will do anything to improve accuracy. However, sensors purchased from different vendors, at different times, tend to have randomly distributed offsets. In that case normalization also improves accuracy, but the only way to know if that has happened is to validate against some external reference. The method described here only corrects differences in Offset [with the B value] & Gain/Sensitivity [the M value]. Most temp sensors have a U-shaped error/noise distributions around their maximum accuracy point and if you see that pattern in the residual plots it means a linear correction is not sufficient and you might need to use a quadratic. Applying a linear correction to only one side of that curve (which I’ve done here) will increase errors on the other side of the distribution but as long as those amplified errors happen outside of the range of temperatures I intend to monitor then the trade-off is acceptable.

Comments:

After the normalization of the Si7051 reference sensors, data from that same run was also used to calibrate the NTC sensors on those new loggers. Instead of using the cooling curve, the NTC calibration is done with a couple of the flat plateaus and physical ice-points collected separately. Being able to graph the large group with data from the same circulation bath makes is easy to identify & fix any copy/paste errors made during the calculations.

It’s interesting that the NTC’s clustered with an offset of about 0.05°C from the normalized Si7051s. I’m attributing this shift to creating the Steinhart-Hart equation constants with a data pair down at 0°C that was not in the normalization dataset.

Out of forty DIY sensors, one (green) outlier above exhibited a slight nonlinearity problem that pushes it above the group at 40°C, but below the group at 19°C. Another exhibited a dramatic step-change behavior throughout the run and this logger will be rejected entirely from temperature sensing. Because our timing based method for reading NTCs treats the entire logger as a system, it will take more testing to see if this problem is with the physical sensor or with the ProMini module.

A rare step-offset problem on one of the loggers where the NTC (blue) diverged & then returned to alignment with the Si7051 (orange) many times during the run. If this sensors data had been included in the normalization average ALL of the residual plots would have shown the pattern.

The higher resolution of the NTCs allowing us to look more closely at the Anova’s PID performance:

Temperature stability of the system near 46°C: Si7051(orange) vs 10k NTC (purple) read every 2minutes. The first couple of hours at each plateau are not really useable for normalization.

I suspect the 16 litre volume being driven here is near the practical limit of the little cookers capability, but the single wobble at the start of each plateau indicates a decent level of control. Especially considering the used cooker was $35 on eBay and the cooler was $10 at our local Goodwill. I’m still scratching my head about why the little Si7051 sensor modules read rising thermal spikes faster than the NTC but changes in the cooling direction stay aligned. I suspect this is due to direct conduction away from the NTC into the larger thermal mass of the ProMini board its attached to. Even with a thin housing that is IR transmissive, its not unusual to see a 15-20 minute lag in the NTC temperature readings relative to ambient.

References & Links:


Identifying Specific Problems Using Residual Plots
PID Controller – MATLAB (Guide) by Mike Deffenbaugh
PID Temperature Controllers: TUTCO Conductive
Anova’s Step-by-Step Guide to Building a Sous Vide Cooler
Considerations for water baths as calibration equipment
Guide on Secondary Thermometry: White et Al (2014)
Calibration of TAO array of moored buoys: NOAA
Normalization applied to a group of Pressure Sensors

Using a DS18B20 Temperature Sensor “without” a dedicated Arduino library

New housings in production

New housings in production. The sensor wells on top will be filled with epoxy to seal the hull.  Note: some of these housings have unnecessary “chunks” of PVC on the outside, as I made them for latch clamps which I am no longer using in the design.

I haven’t had time to post to the blog lately, as I am now in full-on production mode: trying to get five full units running for deployment next week.  Between all the cutting and sanding of the new underwater housings, I have been investigating sensors, and thinking about how to make the entire unit modular enough to allow quick field repairs.  The I2C bus architecture becomes very attractive here, because so many sensors are available for it, and I found a nifty I2C hub from Seed studios, which gives me a standard plug for connectivity. But most of the sensors I found want a steady 3.3 volt supply, and that is not available on the Tiny Duino  (the lack of a 3.3v rail is a weakness of the platform but I was the one who wanted to run with no voltage regulator in the first place..) So I started designing a power supply module around a NCP1402-3.3V Step-Up Breakout from Sparkfun.  I knew this was going to waste 25% of my power for this deployment, but I figured I could use lithium AA’s to make up for the loss, and look around for a more efficient voltage regulator later.

No luck with the voltage regulator even though it seemed to be working fine...

It seemed to be working fine…

Well, a frustrating day or two later and I still had no joy from the 3.3v regulator. I don’t know if it was an inrush current brownout, or transient spikes, or output ripple…but for some reason the tiny stack simply will not run from this regulator – all I get is one little pip out of the on-board led and that’s it.  Grrr! There goes about half of sensors I planned to use with the nice I2C breakout boards I had just purchased, unless I can somehow power the sensors “separately” from the main mcu stack. (And there is no guarantee I won’t have the same kind of gremlins pestering me there…)

Time to make lemonade: I had a few DS18B20 one wire temperature sensors, and they are not too choosy about input voltage, so I figured I would give them a try, while just running the Tiny’s from unregulated AA’s (…again).  DS18B20’s are common as dirt, and almost as cheap, and there are libraries for them everywhere. So it should not take long to get them going…right?

Well what I thought was going to take me 30 minutes has actually taken me a day of digging to sort out, so I am posting the result here, to hopefully save someone else the trouble.  The standard approach is to install a Dallas control library, and a one wire library to run this sensor. Most sources suggested the library written by Miles Burton, and the one wire library over at PJRC.  And after a few grumbles, like finding out that zip file created directories with the wrong names  ( “dallas-temperature-control” from the zip extraction needs to be renamed to “DallasTemperature” for it to work) I did get the test code running…sort of. Now don’t get me wrong, Miles Burton has created a veritable swiss army knife of a library, but a tiny script for this one sensor weighed in at 8,772 bytes. That’s almost a third of the available program memory, and I already knew my data logger script was around 22 k (with accelerometer) , so that was not going to leave much space for the other sensors I want to add.

Logger units with I2C hub for sensor and RTC connections.

Logger units with I2C hub for sensor and RTC connections.

And while I was sorting all that out, I discovered another problem with the DS18b20: Every once and a while it was throwing out a spurious reading of 85 degrees, but it was frustratingly intermittent. More run tests with different settings showed that the error never happened when I had the sensor set to run at 9-bit, but it popped up more frequently as I raised the bit depth. Back to digging through the forums, which revealed that this is a pretty common issue with the DS18b20. The “default” setting of the registers produces the 85, which is what you get if you read the sensor too soon after a reset. If you sift through the datasheet, you find that when you ask the sensor for the full 12 bits of resolution, you need to wait at least 750 ms for the sensor to embed the temperature data in it’s eeprom before you can read it out. So although the sensor only draws 1.5mA during the conversion, and it goes into standby mode right afterwards,  it was going to hold the whole system in limbo for that conversion time, doing some serious damage my the overall power budget.

More googling, and I came across a great little project blog called BitKnitting, where someone managed to use the sensor without a dedicated sensor library. So it was possible! However, they were only using the integer part of the 12 bit register, and I wanted all of the information, as the temperature variations in cave system is often only fractions of a degree C. I found a floating point capture demonstrated over at the Bildr forum. Combining that with a 1 second Watch Dog Timer sleep (to save power while the mcu waits for the sensor’s temperature conversion) produced this little script which weighs in at 5988 bytes. Not much savings on memory, because of the addition of the wdt library, but hopefully much lighter on the projects power budget. I will be weaving this into the main logger code later. I also have a feeling that I can go rooting through those libraries and delete a few functions I am not using to make them lighter, when I have time.

Addendum 2015-02-25

I think this is approaching the largest string I would want to deploy on a dive.

Addendum 2015: I think this is approaching the largest senor string I would want to deploy on a dive.

I recently started making long chains of these sensors, in an attempt to build a poor mans thermistor string. Here is a link to the post about those experiments.

Addendum 

The DS18B20 is only supposed to draw around 1μA when in standby, and not being interrogated. So it hardly seems worth the bother of turning them on and off to me, but there are a few people doing so by powering the units directly from the digital pins of the Arduino.  I might investigate that later if I have not used the pins for something more productive.

Addendum

I have also noticed that this sensor seems to warm itself if you have it doing a continuous reads at 12 bit as the script below does (which has the unit at its maximum 1.5 mA current the entire time). So just be warned that if you are using the ‘waterproof’ models, which are encased in a metal sleeve filled with epoxy, you can’t drive the sensor full tilt without internal heat building up & affecting the readings. 

// adapted from https://github.com/BitKnitting/prototypes/blob/master/SensorNode433/SensorNode433.ino
//  and  http://forum.bildr.org/viewtopic.php?f=17&t=779
//  sleep from http://www.gammon.com.au/forum/?id=11497

#include <avr/sleep.h>
#include <avr/wdt.h>

#include <OneWire.h>
const byte DS18B20_PIN=4;  //sensor data pin
OneWire ds(DS18B20_PIN);
byte addr[8];
float DS18B20float;

void setup() {

Serial.begin(9600);

//Set up Temp sensor – there is only one 1 wire sensor connected
if ( !ds.search(addr)) {
Serial.println(F(“—> ERROR: Did not find the DS18B20  Temperature Sensor!”));
return;
}
else {
Serial.print(F(“DS18B20 ROM address =”));
for(byte i = 0; i < 8; i++) {
Serial.write(‘ ‘);
Serial.print(addr[i], HEX);
}
Serial.println();
}
delay (200);
}

void loop() {

DS18B20float = getTemp();
Serial.print(F(“FLOAT temp in celcius: “));
Serial.println(DS18B20float);
//Note: sensor defaults to a reading of 85 if you read it too soon after a reset!
delay (200);  //just a delay to boot out the coms
}

// watchdog interrupt
ISR (WDT_vect)
{
wdt_disable(); // disable watchdog
} // end of WDT_vect

// this returns the temperature from one DS18S20 in DEG Celsius using 12 bit conversion
float getTemp(){
byte data[2];
ds.reset();
ds.select(addr);
ds.write(0x44); // start conversion, read temperature and store it in the scratchpad

//this next bit creates a 1 second WDT delay during the DS18b20 temp conversion
//The time needed between the CONVERT_T command and the READ_SCRATCHPAD command has to be at least
//750 millisecs (but can be shorter if using a D18B20 type with resolutions < 12 bits)
MCUSR = 0; // clear various “reset” flags
WDTCSR = bit (WDCE) | bit (WDE); // allow changes, disable reset
// set interrupt mode and an interval
WDTCSR = bit (WDIE) | bit (WDP2) | bit (WDP1); //a 1 sec timer
wdt_reset(); // pat the dog
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu ();
sleep_disable(); // cancel sleep after wakeup as a precaution

byte present = ds.reset();  //now we can read the temp sensor data
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for (int i = 0; i < 2; i++) { // Only read the bytes you need? there is more there
data[i] = ds.read();
}
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB); //using two’s compliment
float TemperatureSum = tempRead / 16; //this converts to C
return TemperatureSum;

}