DS18B20 Reliable Reading Methods – Practical Implementation to Avoid Errors and Invalid Values

This is article 7 of 8 in a series about robust DS18B20 / 1-Wire system design.

Please see the introductory topic:

Introduction

Based on comments from gfvalvo and robtillaart in the discussion of that article
"DS18B20 Optical Feedback – Visualizing Conversion Time with an LED"
(special thanks for the inspiration :slightly_smiling_face:), I decided to run a few simple performance tests that anyone can easily reproduce.

The question raised in that discussion was essentially about how efficiently temperature values are obtained from a DS18B20 and whether using the “get by index” functions is a good idea when only one sensor is connected to the bus.

Two points were mentioned:

  • retrieving a sensor by index may internally require a bus scan
  • retrieving it by address avoids this step

Rather than speculating, it seemed useful to simply measure the timing of different approaches.

The following four small test programs compare:

  1. Blocking mode using the DallasTemperature library
  2. Non-blocking mode using the DallasTemperature library
  3. Direct OneWire bus access without the DallasTemperature abstraction
  4. Non-blocking MCU busy time (time required to start and read a measurement)

All tests were run on a typical Arduino Uno setup with a single DS18B20 connected to pin 2.
Each measurement is executed 10 times, and the minimum, maximum and average time are reported.


Test 1

Blocking Mode Time Requirement

This first test uses the DallasTemperature library in blocking mode.
The sensor conversion is completed before the timing measurement begins, so only the temperature read operation itself is measured.

Two methods are compared:

  • getTempCByIndex(0)
  • getTempC(sensorAddress)

The sensor address is retrieved once in setup() and then reused.

Code

/*
  DS18B20 Read Method Comparison
  --------------------------------
  Test 1: Blocking Mode Time Requirement

  Purpose
  -------
  Measure the execution time of two DallasTemperature read methods:
  1) getTempCByIndex()
  2) getTempC(address)

  Conditions
  ----------
  - Blocking mode (waitForConversion = true)
  - Sensor resolution: 12 bit
  - Conversion finished before timing starts
  - Only the read operation is measured
*/

#include <OneWire.h>
#include <DallasTemperature.h>

constexpr uint8_t busPin = 2;
constexpr uint8_t samples = 10;

OneWire oneWire(busPin);
DallasTemperature sensors(&oneWire);

DeviceAddress sensorAddress;

unsigned long timeIndex[samples];
unsigned long timeAddress[samples];

void setup()
{
  Serial.begin(115200);
  Serial.println("DS18B20 Blocking Mode Time Requirement");

  sensors.begin();
  sensors.setWaitForConversion(true);

  if (!sensors.getAddress(sensorAddress, 0))
  {
    Serial.println("Sensor not found");
    while (true);
  }

  sensors.setResolution(sensorAddress, 12);

  sensors.requestTemperatures();
  delay(750);

  Serial.println("Starting measurements...\n");

  for (uint8_t i = 0; i < samples; i++)
  {
    unsigned long t1;
    unsigned long t2;

    t1 = micros();
    sensors.getTempCByIndex(0);
    t2 = micros();
    timeIndex[i] = t2 - t1;

    t1 = micros();
    sensors.getTempC(sensorAddress);
    t2 = micros();
    timeAddress[i] = t2 - t1;
  }

  printResults("getTempCByIndex()", timeIndex);
  printResults("getTempC(address)", timeAddress);
}

void loop()
{
}

void printResults(const char *label, unsigned long data[])
{
  unsigned long minVal = data[0];
  unsigned long maxVal = data[0];
  unsigned long sum = 0;

  for (uint8_t i = 0; i < samples; i++)
  {
    if (data[i] < minVal) minVal = data[i];
    if (data[i] > maxVal) maxVal = data[i];
    sum += data[i];
  }

  float avg = sum / (float)samples;

  Serial.println(label);
  Serial.print("Min: "); Serial.print(minVal); Serial.println(" us");
  Serial.print("Max: "); Serial.print(maxVal); Serial.println(" us");
  Serial.print("Avg: "); Serial.print(avg); Serial.println(" us");
  Serial.println();
}

Result

getTempCByIndex()
Min: 26848 us
Max: 27028 us
Avg: 26869.20 us

getTempC(address)
Min: 12348 us
Max: 12356 us
Avg: 12350.40 us

Test 2

Non-Blocking Mode Time Requirement

The second test repeats the comparison but uses non-blocking mode.

Here the conversion time is handled explicitly using
millisToWaitForConversion().
Again, the timing measurement only covers the temperature read call itself, not the conversion phase.

Code

/*
  DS18B20 Read Method Comparison
  --------------------------------
  Test 2: Non-Blocking Mode Time Requirement

  Purpose
  -------
  Measure the execution time of two DallasTemperature read methods
  in NON-BLOCKING mode:

  1) getTempCByIndex()
  2) getTempC(address)

  Conditions
  ----------
  - waitForConversion = false
  - Sensor resolution: 12 bit
  - Conversion wait handled manually using millisToWaitForConversion()
  - Only the read operation itself is measured

  Method
  ------
  Each method is measured 10 times.
  The program outputs:
    - Minimum time
    - Maximum time
    - Average time

  Hardware
  --------
  Arduino Uno
  DS18B20 connected to pin 2

  Note
  ----
  Temperature conversion is started with requestTemperatures()
  and the program waits explicitly for the required conversion
  time before performing the timed read operation.
*/

#include <OneWire.h>
#include <DallasTemperature.h>

constexpr uint8_t busPin = 2;
constexpr uint8_t samples = 10;

OneWire oneWire(busPin);
DallasTemperature sensors(&oneWire);

DeviceAddress sensorAddress;

uint32_t waitTime;

unsigned long timeIndex[samples];
unsigned long timeAddress[samples];

void setup()
{
  Serial.begin(115200);
  Serial.println("DS18B20 Non-Blocking Mode Time Requirement");

  sensors.setWaitForConversion(false);
  sensors.begin();

  if (!sensors.getAddress(sensorAddress, 0))
  {
    Serial.println("Sensor not found");
    while (true);
  }

  sensors.setResolution(sensorAddress, 12);

  waitTime = sensors.millisToWaitForConversion(sensors.getResolution());

  Serial.println("Starting measurements...\n");

  for (uint8_t i = 0; i < samples; i++)
  {
    unsigned long t1;
    unsigned long t2;

    sensors.requestTemperatures();
    delay(waitTime);

    t1 = micros();
    sensors.getTempCByIndex(0);
    t2 = micros();
    timeIndex[i] = t2 - t1;

    t1 = micros();
    sensors.getTempC(sensorAddress);
    t2 = micros();
    timeAddress[i] = t2 - t1;
  }

  printResults("getTempCByIndex()", timeIndex);
  printResults("getTempC(address)", timeAddress);
}

void loop()
{
}

void printResults(const char *label, unsigned long data[])
{
  unsigned long minVal = data[0];
  unsigned long maxVal = data[0];
  unsigned long sum = 0;

  for (uint8_t i = 0; i < samples; i++)
  {
    if (data[i] < minVal) minVal = data[i];
    if (data[i] > maxVal) maxVal = data[i];
    sum += data[i];
  }

  float avg = sum / (float)samples;

  Serial.println(label);
  Serial.print("Min: "); Serial.print(minVal); Serial.println(" us");
  Serial.print("Max: "); Serial.print(maxVal); Serial.println(" us");
  Serial.print("Avg: "); Serial.print(avg); Serial.println(" us");
  Serial.println();
}

Result

getTempCByIndex()
Min: 26848 us
Max: 26856 us
Avg: 26850.40 us

getTempC(address)
Min: 12348 us
Max: 12356 us
Avg: 12350.80 us

Test 3

OneWire Level Time Requirement

The final test removes the DallasTemperature library entirely and reads the sensor directly on the OneWire protocol level.

The following sequence is timed:

  • bus reset
  • ROM select
  • scratchpad read (9 bytes)

This provides a reference for the pure bus-level transaction time.

Code

/*
  DS18B20 Read Method Comparison
  --------------------------------
  Test 3: OneWire Level Time Requirement

  Purpose
  -------
  Measure the execution time of a DS18B20 temperature read
  performed directly on the OneWire bus without using the
  DallasTemperature library.

  Sequence measured
  -----------------
  - OneWire reset
  - ROM select
  - READ SCRATCHPAD command
  - Reading 9 scratchpad bytes

  Conditions
  ----------
  - Sensor resolution: 12 bit
  - Conversion completed before timing starts
  - Only the OneWire communication is measured

  Method
  ------
  Measurement repeated 10 times.
  The program outputs:
    - Minimum time
    - Maximum time
    - Average time

  Hardware
  --------
  Arduino Uno
  DS18B20 connected to pin 2

  Note
  ----
  This test measures the raw OneWire transaction time
  required to read the sensor scratchpad. It represents
  the lowest-level access used by higher-level libraries.
*/

#include <OneWire.h>

constexpr uint8_t busPin = 2;
constexpr uint8_t samples = 10;

OneWire oneWire(busPin);

byte sensorAddress[8];
byte scratchpad[9];

unsigned long timeBus[samples];

void setup()
{
  Serial.begin(115200);
  Serial.println("DS18B20 OneWire Level Time Requirement");

  if (!oneWire.search(sensorAddress))
  {
    Serial.println("Sensor not found");
    while (true);
  }

  oneWire.reset_search();

  Serial.println("Starting measurements...\n");

  startConversion();
  delay(750);

  for (uint8_t i = 0; i < samples; i++)
  {
    unsigned long t1;
    unsigned long t2;

    t1 = micros();

    oneWire.reset();
    oneWire.select(sensorAddress);
    oneWire.write(0xBE);

    for (uint8_t j = 0; j < 9; j++)
    {
      scratchpad[j] = oneWire.read();
    }

    t2 = micros();

    timeBus[i] = t2 - t1;
  }

  printResults("OneWire scratchpad read", timeBus);
}

void loop()
{
}

void startConversion()
{
  oneWire.reset();
  oneWire.select(sensorAddress);
  oneWire.write(0x44);
}

void printResults(const char *label, unsigned long data[])
{
  unsigned long minVal = data[0];
  unsigned long maxVal = data[0];
  unsigned long sum = 0;

  for (uint8_t i = 0; i < samples; i++)
  {
    if (data[i] < minVal) minVal = data[i];
    if (data[i] > maxVal) maxVal = data[i];
    sum += data[i];
  }

  float avg = sum / (float)samples;

  Serial.println(label);
  Serial.print("Min: "); Serial.print(minVal); Serial.println(" us");
  Serial.print("Max: "); Serial.print(maxVal); Serial.println(" us");
  Serial.print("Avg: "); Serial.print(avg); Serial.println(" us");
  Serial.println();
}

Result

OneWire scratchpad read
Min: 11284 us
Max: 11284 us
Avg: 11284.00 us

Test 4

Non-Blocking MCU Busy Time

The fourth test looks at a slightly different aspect of the non-blocking approach: how long the microcontroller is actually busy when interacting with the sensor.

Instead of measuring only the read operation, this test measures three parts separately:

  • requestTemperatures() – time required to start the measurement
  • getTempC(address) – time required to read the result
  • Total MCU busy time – start + read

During the sensor’s internal conversion (~750 ms at 12-bit resolution) the DS18B20 works autonomously and the MCU is free to perform other tasks. This additional measurement was included as suggested by robtillaart, to illustrate how much processor time is actually occupied versus how much time remains available for parallel work.

Code

/*
  DS18B20 Read Method Comparison
  --------------------------------
  Test 4: Non-Blocking MCU Busy Time

  Purpose
  -------
  Measure how long the MCU is actually busy when using the
  DS18B20 in non-blocking mode.

  The following operations are timed separately:

  1) requestTemperatures()  -> start conversion
  2) getTempC(address)      -> read temperature
  3) total MCU busy time

  Conditions
  ----------
  - waitForConversion = false
  - Sensor resolution: 12 bit
  - Conversion time handled manually
  - Only MCU execution time is measured

  Method
  ------
  Measurement repeated 10 times.
  The program outputs:

    - Minimum time
    - Maximum time
    - Average time

  Hardware
  --------
  Arduino Uno
  DS18B20 connected to pin 2

  Note
  ----
  During the sensor conversion (~750 ms at 12-bit resolution)
  the DS18B20 performs the measurement internally. The MCU
  can execute other tasks during this period.
*/

#include <OneWire.h>
#include <DallasTemperature.h>

constexpr uint8_t busPin = 2;
constexpr uint8_t samples = 10;

OneWire oneWire(busPin);
DallasTemperature sensors(&oneWire);

DeviceAddress sensorAddress;

uint32_t waitTime;

unsigned long timeRequest[samples];
unsigned long timeRead[samples];
unsigned long timeTotal[samples];

void setup()
{
  Serial.begin(115200);
  Serial.println("DS18B20 Non-Blocking MCU Busy Time");

  sensors.setWaitForConversion(false);
  sensors.begin();

  if (!sensors.getAddress(sensorAddress, 0))
  {
    Serial.println("Sensor not found");
    while (true);
  }

  sensors.setResolution(sensorAddress, 12);

  waitTime = sensors.millisToWaitForConversion(sensors.getResolution());

  Serial.println("Starting measurements...\n");

  for (uint8_t i = 0; i < samples; i++)
  {
    unsigned long t1;
    unsigned long t2;

    // measure start of conversion

    t1 = micros();
    sensors.requestTemperatures();
    t2 = micros();

    timeRequest[i] = t2 - t1;

    // wait for sensor conversion (MCU free)

    delay(waitTime);

    // measure read operation

    t1 = micros();
    sensors.getTempC(sensorAddress);
    t2 = micros();

    timeRead[i] = t2 - t1;

    // total MCU busy time

    timeTotal[i] = timeRequest[i] + timeRead[i];
  }

  printResults("requestTemperatures()", timeRequest);
  printResults("getTempC(address)", timeRead);
  printResults("Total MCU busy time", timeTotal);
}

void loop()
{
}

void printResults(const char *label, unsigned long data[])
{
  unsigned long minVal = data[0];
  unsigned long maxVal = data[0];
  unsigned long sum = 0;

  for (uint8_t i = 0; i < samples; i++)
  {
    if (data[i] < minVal) minVal = data[i];
    if (data[i] > maxVal) maxVal = data[i];
    sum += data[i];
  }

  float avg = sum / (float)samples;

  Serial.println(label);
  Serial.print("Min: "); Serial.print(minVal); Serial.println(" us");
  Serial.print("Max: "); Serial.print(maxVal); Serial.println(" us");
  Serial.print("Avg: "); Serial.print(avg); Serial.println(" us");
  Serial.println();
}

Result

requestTemperatures()
Min: 2088 us
Max: 2272 us
Avg: 2108.00 us

getTempC(address)
Min: 12344 us
Max: 12344 us
Avg: 12344.00 us

Total MCU busy time
Min: 14432 us
Max: 14616 us
Avg: 14452.00 us

Preliminary Conclusion

Even with this very simple setup, the measurements already show a few interesting points:

  • Direct addressing is noticeably faster than reading by index.
  • Blocking vs. non-blocking mode does not significantly affect the read time itself.
  • The raw OneWire transaction is faster than the full DallasTemperature call stack.
  • In non-blocking mode the MCU is actually busy only for a very small fraction of the total sensor cycle; most of the time is spent inside the sensor’s internal conversion process.

Exactly how much this matters in a real application probably depends on the specific design goals. In many projects the difference may be negligible, while in others (for example when reading many sensors or operating on tight timing budgets) it could become relevant.

Either way, these small tests might provide a useful starting point for further discussion — and of course I’d be interested to see results from other setups as well.


Thanks for this test, very informative

One time is missing, the non blocking call, how long the mcu is busy to do the request and reading. So how much micros can be spent on other task "in parallel".

Other than inserting fixed delay of 750 ms convrsion time, let the sensor take as much time as it needs for the conversion. The codes are:

ds.reset();
while(ds.read() != 0xFF)
{
    ;// wait or do somethiing else
}

That code snippet is so incomplete and lacking context that it's meaningless.

The question of interest is not how long the sensor takes to do a reading. If getting the temperature as quickly as possible were a concern, you wouldn't be using a DS18B20 in the first place. The interesting question ... What is the best technique to allow your processor to do as many other useful things as possible while the sensor is "thinking" and what is the quickest way manipulate the (very slow) OneWire bus?

You are welcome!
Good point.
My current tests only measure the read operation itself.
A fourth test measuring requestTemperatures() + getTempC() in non-blocking mode would indeed show how much CPU time is actually occupied versus the 750 ms sensor conversion time.
I might add that measurement as well.

Agree, definitely not at 12 bit resolution.

resolution duration (ms)
12 750
11 375
10 188
9 94

Done!
See edit "Test 4".

@InquisitiveMind

The blocking is done in the request part so the test-1 should be rewritten imho

/*
  DS18B20 Read Method Comparison
  --------------------------------
  Test 1: Blocking Mode Time Requirement

  Purpose
  -------
  Measure the execution time of two DallasTemperature read methods:
  1) getTempCByIndex()
  2) getTempC(address)

  Conditions
  ----------
  - Blocking mode (waitForConversion = true)
  - Sensor resolution: 12 bit
  - Conversion finished before timing starts
  - Only the read operation is measured
*/

#include <OneWire.h>
#include <DallasTemperature.h>

constexpr uint8_t busPin = 2;
constexpr uint8_t samples = 10;

OneWire oneWire(busPin);
DallasTemperature sensors(&oneWire);

DeviceAddress sensorAddress;

unsigned long timeIndex[samples];
unsigned long timeAddress[samples];

void setup()
{
  Serial.begin(115200);
  Serial.println("DS18B20 Blocking Mode Time Requirement");

  sensors.begin();
  sensors.setWaitForConversion(true);

  if (!sensors.getAddress(sensorAddress, 0))
  {
    Serial.println("Sensor not found");
    while (true);
  }

  sensors.setResolution(sensorAddress, 12);

  Serial.println("Starting measurements...\n");

  for (uint8_t i = 0; i < samples; i++)
  {
    unsigned long t1;
    unsigned long t2;

    t1 = micros();
    sensors.requestTemperatures();
    sensors.getTempCByIndex(0);
    t2 = micros();
    timeIndex[i] = t2 - t1;

    t1 = micros();
    sensors.requestTemperatures();
    sensors.getTempC(sensorAddress);
    t2 = micros();
    timeAddress[i] = t2 - t1;
  }

  printResults("getTempCByIndex()", timeIndex);
  printResults("getTempC(address)", timeAddress);
}

void loop()
{
}

void printResults(const char *label, unsigned long data[])
{
  unsigned long minVal = data[0];
  unsigned long maxVal = data[0];
  unsigned long sum = 0;

  for (uint8_t i = 0; i < samples; i++)
  {
    if (data[i] < minVal) minVal = data[i];
    if (data[i] > maxVal) maxVal = data[i];
    sum += data[i];
  }

  float avg = sum / (float)samples;

  Serial.println(label);
  Serial.print("Min: "); Serial.print(minVal); Serial.println(" us");
  Serial.print("Max: "); Serial.print(maxVal); Serial.println(" us");
  Serial.print("Avg: "); Serial.print(avg); Serial.println(" us");
  Serial.println();
}

I did so and the result is confusing:

getTempCByIndex()
Min: 29008 us
Max: 650528 us
Avg: 394688.40 us
getTempC(address)
Min: 14508 us
Max: 607088 us
Avg: 251521.20 us

... need some more time.

The 650 makes sense as this is including the conversion time.

The docs states you need to wait for 750, but that is sort of MAX conversion time, not a typical one.

The minimum times I cannot explain right away.

Just a suspicion: it might be that a new request is started while a previous conversion is still running.

Edit:
It looks like the library does not actually block for the full conversion in this test setup, so the measurement mostly captures the read operation itself. Interestingly, this still clearly shows the overhead of reading by index versus using the stored address.

Not entirely sure what the best way to structure Test 1 would be in this case. Maybe someone can figure it out and post a suitable version.

Added two delay(1000) so expect (assume/wish) that the measurements do not interfere.

/*
  DS18B20 Read Method Comparison
  --------------------------------
  Test 1: Blocking Mode Time Requirement

  Purpose
  -------
  Measure the execution time of two DallasTemperature read methods:
  1) getTempCByIndex()
  2) getTempC(address)

  Conditions
  ----------
  - Blocking mode (waitForConversion = true)
  - Sensor resolution: 12 bit
  - Conversion finished before timing starts
  - Only the read operation is measured
*/

#include <OneWire.h>
#include <DallasTemperature.h>

constexpr uint8_t busPin = 2;
constexpr uint8_t samples = 10;

OneWire oneWire(busPin);
DallasTemperature sensors(&oneWire);

DeviceAddress sensorAddress;

unsigned long timeIndex[samples];
unsigned long timeAddress[samples];

void setup()
{
  Serial.begin(115200);
  Serial.println("DS18B20 Blocking Mode Time Requirement");

  sensors.begin();
  sensors.setWaitForConversion(true);

  if (!sensors.getAddress(sensorAddress, 0))
  {
    Serial.println("Sensor not found");
    while (true);
  }

  sensors.setResolution(sensorAddress, 12);

  Serial.println("Starting measurements...\n");

  for (uint8_t i = 0; i < samples; i++)
  {
    unsigned long t1;
    unsigned long t2;

    delay(1000);

    t1 = micros();
    sensors.requestTemperatures();
    sensors.getTempCByIndex(0);
    t2 = micros();
    timeIndex[i] = t2 - t1;

    delay(1000);

    t1 = micros();
    sensors.requestTemperatures();
    sensors.getTempC(sensorAddress);
    t2 = micros();
    timeAddress[i] = t2 - t1;
  }

  printResults("getTempCByIndex()", timeIndex);
  printResults("getTempC(address)", timeAddress);
}

void loop()
{
}

void printResults(const char *label, unsigned long data[])
{
  unsigned long minVal = data[0];
  unsigned long maxVal = data[0];
  unsigned long sum = 0;

  for (uint8_t i = 0; i < samples; i++)
  {
    if (data[i] < minVal) minVal = data[i];
    if (data[i] > maxVal) maxVal = data[i];
    sum += data[i];
  }

  float avg = sum / (float)samples;

  Serial.println(label);
  Serial.print("Min: "); Serial.print(minVal); Serial.println(" us");
  Serial.print("Max: "); Serial.print(maxVal); Serial.println(" us");
  Serial.print("Avg: "); Serial.print(avg); Serial.println(" us");
  Serial.println();
}

Tested as provided:

DS18B20 Blocking Mode Time Requirement
Starting measurements...

getTempCByIndex()
Min: 29004 us
Max: 29016 us
Avg: 29009.60 us

getTempC(address)
Min: 14508 us
Max: 636164 us
Avg: 138839.20 us

Interesting — the results are still a bit mixed.
Most runs again show ~29 ms vs ~14 ms, which looks very similar to the pure read timing measured in the earlier tests.
But occasionally a much larger value appears (around 600 ms), which suggests that requestTemperatures() sometimes actually waits for the remaining conversion time.

So it seems the measurement sometimes catches the sensor while a conversion is already in progress, and sometimes not. In any case the index vs. address overhead still shows up very clearly.

SOLUTION / EXAMPLES

Even though there may still be further discussion about the exact timing measurements, the practical winner is already quite clear.

Based on the valuable input from gfvalvo and robtillaart, together with my own experience from real-world applications, here are two example sketches (multi-sensor and single-sensor). In my view they summarize the most useful lessons from the discussion and provide a solid improvement in terms of robustness, speed, simplicity, and overall convenience.

The most important highlights are:

  • One bus per sensor, each with its own (adaptable) pull-up resistor. This improves signal integrity and significantly increases robustness.
  • Physical star wiring topology becomes practical, for example when using relatively inexpensive metal-encapsulated sensors with long cables, without easily running into bus stability problems.
  • Direct use of the sensor address for maximum speed, without the need to hard-code the address or even know it beforehand.
  • Non-blocking mode, allowing the MCU to continue executing other code while the unavoidable temperature conversion is running.
  • Maximum convenience, because a sensor can be replaced without any code modification, even though each sensor has its own unique address.
  • No repeated bus scanning, which avoids unnecessary overhead and keeps timing deterministic.
  • Clean and predictable program structure, which makes the code easy to maintain and expand later.

Multi Sensor Example

This example demonstrates three sensors, each connected to its own OneWire bus pin.
The addresses are discovered once during setup() and then used directly for all subsequent reads.

/*
  DS18B20 One Sensor Per Bus Example
  ----------------------------------
  Purpose
  -------
  Demonstrate how to read multiple DS18B20 sensors, each on its own OneWire bus,
  using non-blocking mode, so the MCU can perform other tasks while the sensors
  perform temperature conversion.

  Features
  --------
  - Three sensors, each on a dedicated bus pin
  - Store addresses once in setup
  - Use non-blocking conversion
  - Measure appropriate wait time per sensor resolution
  - Print temperatures continuously

  Hardware
  --------
  Arduino Uno (or compatible)
  3 DS18B20 sensors on separate pins (with pull-up resistors)
*/

#include <OneWire.h>
#include <DallasTemperature.h>

// Define pins for each sensor bus
constexpr uint8_t BUS_PINS[] = {2, 3, 4};
constexpr uint8_t NUM_SENSORS = sizeof(BUS_PINS);

// Create OneWire and DallasTemperature objects for each bus
OneWire oneWireBuses[NUM_SENSORS] = {OneWire(BUS_PINS[0]), OneWire(BUS_PINS[1]), OneWire(BUS_PINS[2])};
DallasTemperature sensors[NUM_SENSORS] = {DallasTemperature(&oneWireBuses[0]),
                                         DallasTemperature(&oneWireBuses[1]),
                                         DallasTemperature(&oneWireBuses[2])};

// Store sensor addresses for each bus
DeviceAddress sensorAddresses[NUM_SENSORS];

// Store conversion wait time for each sensor
uint32_t waitTimes[NUM_SENSORS];

// Timer to track when each sensor is ready
unsigned long lastRequest[NUM_SENSORS];

void setup() {
  Serial.begin(115200);
  Serial.println("DS18B20 One Sensor Per Bus Example - Non-Blocking Mode");

  for (uint8_t i = 0; i < NUM_SENSORS; i++) {
    sensors[i].begin();                  
    sensors[i].setWaitForConversion(false); 
    sensors[i].setResolution(12);        

    waitTimes[i] = sensors[i].millisToWaitForConversion(12);
    lastRequest[i] = 0;

    // Get the address of the first sensor on this bus
    if (!sensors[i].getAddress(sensorAddresses[i], 0)) {
      Serial.print("Sensor not found on bus ");
      Serial.println(BUS_PINS[i]);
      while(true) delay(10);
    }

    // Start the first conversion immediately
    sensors[i].requestTemperatures();
  }

  Serial.println("Setup complete - starting readings...\n");
}

void loop() {

  unsigned long now = millis();

  for (uint8_t i = 0; i < NUM_SENSORS; i++) {

    // If the required conversion time has passed,
    // the result is ready to be read.
    if (now - lastRequest[i] >= waitTimes[i]) {

      float tempC = sensors[i].getTempC(sensorAddresses[i]);

      Serial.print("Bus ");
      Serial.print(BUS_PINS[i]);
      Serial.print(": ");
      Serial.print(tempC);
      Serial.println(" °C");

      // Immediately start the next conversion cycle
      sensors[i].requestTemperatures();

      lastRequest[i] = now;
    }
  }

  // Other useful tasks can run here while sensors convert
}

How it works

Dedicated bus per sensor:
Each sensor has its own OneWire bus pin, so there are no addressing conflicts.

Store addresses:
The unique 64-bit address is read once in setup() and reused later.

Non-blocking mode:
setWaitForConversion(false) prevents the MCU from blocking during conversion.

Wait-time calculation:
millisToWaitForConversion() determines the correct conversion time for the selected resolution.

Loop scheduling:
The loop simply checks whether the required conversion time has passed.

Direct temperature read:
Using getTempC(address) avoids repeated bus scanning and gives the fastest access.


Single Sensor Example

For many projects only one sensor is required.
The following sketch is therefore an even simpler version of the same concept.

It removes the loops and arrays used in the multi-sensor example while keeping all the advantages:

  • direct address access
  • non-blocking operation
  • automatic conversion timing
  • easy sensor replacement
/*
  DS18B20 Single Sensor Example
  ------------------------------
  Purpose
  -------
  Simple and practical example for reading a single DS18B20 sensor
  using non-blocking mode and direct address access.

  Features
  --------
  - One sensor on a dedicated OneWire bus
  - Address detected automatically during setup
  - Non-blocking conversion
  - Automatic wait-time calculation
  - Clean and minimal code structure

  Hardware
  --------
  Arduino Uno (or compatible)
  One DS18B20 sensor connected to pin 2
*/

#include <OneWire.h>
#include <DallasTemperature.h>

constexpr uint8_t BUS_PIN = 2;

OneWire oneWire(BUS_PIN);
DallasTemperature sensor(&oneWire);

DeviceAddress sensorAddress;

uint32_t waitTime;
unsigned long lastRequest = 0;

void setup() {

  Serial.begin(115200);
  Serial.println("DS18B20 Single Sensor Example - Non-Blocking Mode");

  sensor.begin();
  sensor.setWaitForConversion(false);
  sensor.setResolution(12);

  if (!sensor.getAddress(sensorAddress, 0)) {
    Serial.println("Sensor not found");
    while(true);
  }

  waitTime = sensor.millisToWaitForConversion(12);

  // Start first conversion
  sensor.requestTemperatures();

  Serial.println("Setup complete - starting readings...\n");
}

void loop() {

  unsigned long now = millis();

  // Check whether the conversion time has passed
  if (now - lastRequest >= waitTime) {

    float tempC = sensor.getTempC(sensorAddress);

    Serial.print("Temperature: ");
    Serial.print(tempC);
    Serial.println(" °C");

    // Start next conversion cycle
    sensor.requestTemperatures();

    lastRequest = now;
  }

  // Other code can run here
}

Difference compared to the multi-sensor example

The single-sensor version is simply a shorter and easier variant of the same concept.

  • no arrays
  • no loops
  • minimal structure

However, it still provides the same practical advantages:

  • non-blocking operation
  • direct address access
  • robust bus setup
  • automatic conversion timing

If additional sensors are needed later, the multi-sensor structure can be used immediately.


If the provided code (please remember: use at your own risk) helped someone with their project, I would really appreciate some feedback — especially if you could briefly mention which part of the approach turned out to be useful in practice.

//
//

And please consider especially this:

Not according to Maxim.
It's the most problematic configuration for a onewire system and they don't guarantee reliability.
Probably your whole problem all along.

Please provide a reference for that assertion.

It's in one of the Maxim app notes on onewire bus.
You can find the app notes on the Analog Devices website.

If you are interested in designing a reliable and error free 1-wire system, I suggest you spend some time reading the app notes. They include both hardware and software recommendations.

"Physical star topology becomes practical" was changed to "Physical star wiring topology becomes practical" to clarify that this refers to the wiring layout rather than the electrical bus topology.

What is the difference?
There is only one wire.