This is article 2 of 8 in a series about robust DS18B20 / 1-Wire system design.
Please see the introductory topic:
The DS18B20 is one of the most widely used digital temperature sensors in microcontroller projects. It is inexpensive, easy to use, and normally very reliable.
However, in real systems several practical issues can appear:
- temporary 1-Wire bus disturbances
- occasional invalid readings (
-127.0,85.0) - conversion delays (up to 750 ms at 12-bit resolution)
- short periods where no valid measurement is available
Many sketches simply check for an error code and ignore the reading:
if (temp == -127) ignore
This approach works in simple demonstrations but becomes problematic in real applications.
If a reading fails exactly at the moment when a control decision must be made (heater, pump, etc.), the software suddenly has no reliable value to work with.
A practical solution is to introduce a very small intermediate layer between the sensor and the control logic.
Instead of using the raw sensor value directly, we:
- Normalize obvious sensor errors
- Feed valid values into a small temperature model
- Ask the model for a safe value
The model maintains a short history of recent values and estimates the current temperature trend.
If a measurement temporarily fails, the model can provide a physically plausible predicted value for a limited time. If prediction becomes unsafe, the system returns NAN.
From the user’s perspective the workflow remains extremely simple:
tempC = sensor.getTempC... // as usual
feedSafeTempModel(sanitizeTemp(tempC)); // immediately feed the model
safeTemp = getSafeTemp(); // use this value from now on
Either a valid temperature is returned or NAN.
Complete Example Sketch (ready to compile)
/* -------------------------------------------------------
DS18B20 Safe Temperature Model - Example Sketch
-------------------------------------------------------
Purpose:
- Demonstrates the use of sanitizeTemp() and Safe Temperature Model
- Provides a simple, reliable temperature value for control logic
- Handles temporary sensor errors, invalid readings, and prediction
- Example is ready to integrate into your existing DS18B20 sketch
------------------------------------------------------- */
#include <OneWire.h>
#include <DallasTemperature.h>
#include <math.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
float safeTemp;
/* -------------------------------------------------------
Normalize obvious sensor errors
------------------------------------------------------- */
float sanitizeTemp(float t) // use this function to normalize errors by definition
{
// sensor error code (exactly -127.0)
if (t == -127.0) return NAN;
// sensor start value (exactly 85.0, almost never a real temperature)
if (t == 85.0) return NAN;
// physically no valid values
// DS18B20 nominally: -55 to +125 °C (use at least this as default!)
// depending on your application choose your limits for a plausibility check
if (t < 10.0 || t > 90.0) return NAN; // as an example!
// just a valid value
return t;
}
/* -------------------------------------------------------
Safe Temperature Model - Global Variables etc. (outside the code)
------------------------------------------------------- */
constexpr uint8_t HISTORY = 5;
constexpr uint8_t MAX_PREDICT_STEPS = 3;
constexpr bool STRICT_SAFETY_MODE = true;
constexpr float MAX_TEMP_SLOPE = 1.5;
constexpr unsigned long MAX_VALUE_AGE = 10000; // milliseconds
float valueHist[HISTORY];
unsigned long timeHist[HISTORY];
uint8_t histIndex = 0;
bool histFull = false;
uint8_t predictSteps = 0;
/* -------------------------------------------------------
Safe Temperature Model - all functions
------------------------------------------------------- */
void feedSafeTempModel(float v)
{
if (isnan(v)) return;
unsigned long now = millis();
valueHist[histIndex] = v;
timeHist[histIndex] = now;
histIndex = (histIndex + 1) % HISTORY;
if (histIndex == 0) histFull = true;
predictSteps = 0;
}
float computeSlope(uint8_t n)
{
float sumX=0,sumY=0,sumXY=0,sumXX=0;
for(uint8_t i=0;i<n;i++)
{
float x = (timeHist[i] - timeHist[0]) / 1000.0;
float y = valueHist[i];
sumX += x;
sumY += y;
sumXY += x*y;
sumXX += x*x;
}
float denom = n*sumXX - sumX*sumX;
if(denom == 0) return 0;
return (n*sumXY - sumX*sumY) / denom;
}
float getSafeTemp()
{
uint8_t count = histFull ? HISTORY : histIndex;
if(count == 0) return NAN;
uint8_t last = (histIndex + HISTORY - 1) % HISTORY;
float lastVal = valueHist[last];
unsigned long lastTime = timeHist[last];
unsigned long now = millis();
// too old → unsafe => NAN
if(now - lastTime > MAX_VALUE_AGE)
return NAN;
// stop prediction if already too many steps
if(predictSteps >= MAX_PREDICT_STEPS)
{
if(STRICT_SAFETY_MODE) return NAN;
return lastVal;
}
// if only one value, return it
if(count < 2)
return lastVal;
float slope;
if(count == 2)
{
uint8_t prev = (last + HISTORY - 1) % HISTORY;
float dt = (timeHist[last] - timeHist[prev]) / 1000.0;
slope = (valueHist[last] - valueHist[prev]) / dt;
}
else
slope = computeSlope(count);
// constrain slope to physically reasonable values
slope = constrain(slope, -MAX_TEMP_SLOPE, MAX_TEMP_SLOPE);
// predict current value
float predicted = lastVal + slope * ((now - lastTime) / 1000.0);
predictSteps++;
return predicted;
}
/* ------------------------------------------------------- */
void setup()
{
Serial.begin(115200);
sensors.begin();
}
void loop()
{
sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);
feedSafeTempModel(sanitizeTemp(tempC));
safeTemp = getSafeTemp();
if(!isnan(safeTemp))
Serial.println(safeTemp);
else
Serial.println("No safe value");
delay(1000);
}
Parameter Block Explanation
constexpr uint8_t HISTORY = 5;
constexpr uint8_t MAX_PREDICT_STEPS = 3;
constexpr bool STRICT_SAFETY_MODE = true;
constexpr float MAX_TEMP_SLOPE = 1.5;
constexpr unsigned long MAX_VALUE_AGE = 10000; // milliseconds
| Parameter | Default | Purpose / Effect | Practical Notes |
|---|---|---|---|
HISTORY |
5 | Number of previous valid values stored for prediction | Increasing this value can improve slope estimation but uses more memory. 5–10 is usually sufficient. |
MAX_PREDICT_STEPS |
3 | Maximum number of consecutive predicted values the model will output without a new sensor reading | Prevents runaway predictions if sensor fails; 3 steps = ~3 seconds if loop runs every second. |
STRICT_SAFETY_MODE |
true | If true, the model returns NAN when prediction is no longer safe; if false, last valid value is returned |
Set to false only if a temporary stale value is preferable over NAN. |
MAX_TEMP_SLOPE |
1.5 °C/s | Maximum allowed temperature change per second | Protects against unrealistic predictions; adjust to your physical system’s limits. |
MAX_VALUE_AGE |
10000 ms | Maximum age of a stored value before it is considered too old | Ensures that old measurements are not used for prediction; adjust based on your sampling rate. |
Don't forget to adjust this "t < 10.0 || t > 90.0"
float sanitizeTemp(float t)
{
...
if (t < 10.0 || t > 90.0) return NAN; // as an example!
...
}
Usage tip:
For most Arduino hobby projects:
- Keep
HISTORY = 5–6 MAX_PREDICT_STEPS = 2–3STRICT_SAFETY_MODE = true(recommended for safety-critical systems)MAX_TEMP_SLOPEcan be set according to the physical system (e.g., for a small heater 1.5–2 °C/s)MAX_VALUE_AGEslightly longer than your main loop cycle to tolerate small delays
This setup ensures a robust, reliable temperature value, even if the sensor occasionally fails or produces implausible readings.
Integration Steps for Existing Sketches
- Copy the global variables block (Safe Temperature Model globals, HISTORY, MAX_PREDICT_STEPS, etc.) outside any function
- Copy all functions (
sanitizeTemp(),feedSafeTempModel(),computeSlope(),getSafeTemp()) outsidesetup()andloop() - Feed valid temperature values after reading the sensor:
feedSafeTempModel(sanitizeTemp(tempC));
- Use the safe temperature whenever a decision must be made:
float safeTemp = getSafeTemp();
if (!isnan(safeTemp))
{
if (safeTemp < TempDesired)
{
Relay = ON;
}
else
{
Relay = OFF;
}
}
else
{
Relay = OFF;
}
This method guarantees that your control logic always has a plausible, safe temperature value, even if a sensor reading fails temporarily. +++ Use at you own risk! +++
This provides a practical, easy-to-use safe temperature layer suitable for all Arduino projects where robustness and predictability matter.

