DS18B20 Wrong Readings – Safe Temperature Model for Reliable Output Despite Errors

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:

  1. Normalize obvious sensor errors
  2. Feed valid values into a small temperature model
  3. 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–3
  • STRICT_SAFETY_MODE = true (recommended for safety-critical systems)
  • MAX_TEMP_SLOPE can be set according to the physical system (e.g., for a small heater 1.5–2 °C/s)
  • MAX_VALUE_AGE slightly 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

  1. Copy the global variables block (Safe Temperature Model globals, HISTORY, MAX_PREDICT_STEPS, etc.) outside any function
  2. Copy all functions (sanitizeTemp(), feedSafeTempModel(), computeSlope(), getSafeTemp()) outside setup() and loop()
  3. Feed valid temperature values after reading the sensor:
    feedSafeTempModel(sanitizeTemp(tempC));
  1. 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.

2 Likes

Thanks for sharing!

1 Like

I don’t see how using old or fake or predicted data makes a system ‘robust’.

Garbage in garbage out. If the data going in is wrong then the output will also be wrong.

This is a very important proposition and deserves a solution, which the OP intends to provide. Congratulation!

1 Like

Just so everyone knows:
85.0 degrees is actually the default starting temperature after a reset and before a valid read - 85.0 degrees may actually be a valid return depending on the application and NOT an error. If you continued getting 85.0 degrees reading when you expected some movement, then maybe it indicates your DS18B20 is being reset.

Errors are indicated by values <= -127 , there are error codes other than -127.

Pullup Resistor guide

    Length       5.0 Volt  3.3 Volt
    10cm (4")     10K0      6K8
    20cm (8")     8K2       4K7
    50cm (20")    4K7       3K3
    100cm (3'4")  3K3       2K2
    200cm (6'8")  2K2       1K0

EDIT: well that's a shame, the latest iteration of the DS18B20 datasheet doesnt even list -127 as an error code, nor have any mention of errors except related to CRC. I'll continue to try and find the reference to error values that I'd seen.

The basics from one of @robtillaart 's libraries

-127 -> DISCONNECTED
-128 -> CRC error
-129 -> POR error
-130 -> GND error

I'll continue the search for where I saw these officially listed

Is there any 1-Wire command to read/capture these error code similar to sending 0x44 to the sensor to begin ADC conversion?

They are returned instead of a valid temperature. It might depend on the onewire library being used - I would have to check more. I just wanted to make it clear the "== -127" might not be catching all error states.

I'll continue the search for where I saw these officially listed

Don't search, there is no such official place, the closest thing is the datasheet.

From DS18B20_RT => DS18B20.h file

//  Error Codes
const int DEVICE_DISCONNECTED = -127;
const int DEVICE_CRC_ERROR    = -128;
const int DEVICE_POR_ERROR    = -129;
const int DEVICE_GND_ERROR    = -130;  //  parasitic power Vdd must GND

These are numbers that are only meaningful in the context of this library.
The -127 is inherited from the Dallas Temperature Control Library.


The temperature register (scratchpad) is a 16 bit value. Whatever happens there are always 16 bits in this register. According to the datasheet it must be interpreted as a signed 16 bit.
So it can hold a value from -32768 to +32767).

The range of the temperatures = [-55°C ... 125°C] or [-67°F to +257°F]
As the accuracy is max 1/16th degree there are (125 +55) * 16 = 2880 values.

So only 2880 of the 65535 values have a meaning as temperature. There is a formula to convert the 16 bit number into a temperature Celsius.

One bit pattern has a double meaning, the one that converts to 85°C.

In the Dallas Temperature Control Library (4.0.5) there is this section

// Error Codes
#define DEVICE_DISCONNECTED_C -127
#define DEVICE_DISCONNECTED_F -196.6
#define DEVICE_DISCONNECTED_RAW -7040

#define DEVICE_FAULT_OPEN_C -254
#define DEVICE_FAULT_OPEN_F -425.199982
#define DEVICE_FAULT_OPEN_RAW -32512

#define DEVICE_FAULT_SHORTGND_C -253
#define DEVICE_FAULT_SHORTGND_F -423.399994
#define DEVICE_FAULT_SHORTGND_RAW -32384

#define DEVICE_FAULT_SHORTVDD_C -252
#define DEVICE_FAULT_SHORTVDD_F -421.599976
#define DEVICE_FAULT_SHORTVDD_RAW -32256

A number of constants defined that indicate errors either when communicating the 1-Wire protocol with devices supported by this library.

A snippet of code where the -127 value is used

    if (!getAddress(deviceAddress, index)) {
        return DEVICE_DISCONNECTED_C;
    }

So that is the origin of the -127.
It is a number out of range of the valid temperatures to indicate an error.
So it comes from a design decision when this library was written.

The DEVICE_DISCONNECTED_F is a Fahrenheit conversion of the -127
The DEVICE_DISCONNECTED_RAW is the 16 bit value which converted to °C is -127

The DEVICE_FAULT* errors are - if I recall correctly - errors for non DS18B20 sensors.

So search no more, it is in the code.

OK thanks Rob - so these other error values are only produced by a specific library.
I was looking through the code and old PR's :slight_smile: it's all education.

A bus reset does not cause the temperature register to be reloaded, only a power on reset will cause that to happen and it is highly unlikely that a bus master would continually read 85 if the device is sporadically being powered up/down.

Pullup Resistor guide

Maxim never recommended a pullup resistor of less greater than 5K for any 1-wire device no matter what the bus length or pullup voltage. What is the source of your recommendations?

Do you mean the following software reset which executes the timing functions of Fig-1?

ds.reset();

image
Figure-1:

I have no idea what ds.reset() does but what you show in the diagram is what I meant by a bus eset.

1 Like

But Analog Devices certainly does.

ds.reset(); is a command of OneWire.h Library to reset the DS18B20 sensor.

#include<OneWire.h>

OneWire ds(7);
byte address[8];

void setup()
{
    ds.reaset();
    ds.search(address);
}

void loop(){}

If you say so.

Thanks I did mean to say greater than.

You can look here:
OneWire-master (2).zip (22.0 KB)

I don't need to look because I don't care what ds.reset() does.

You would certainly care if you would play with DS18B20 sensor?