DS18B20 temperature sensor using OneWire library

In the course of trying to educate myself on the use of the DS18B20 sensor and the OneWire library I have generated some small code which is supposed to indicate the time it takes for a temperature conversion at various output resolutions. There are some anomalies and I would appreciate some help homing in on where I have gone wrong. There is only one device on the OneWire bus.
The code is supposed to: 1. Reset the bus; 2. Issue a SkipRom command (0xCC); 3. Issue a Convert command (0x44); 4. Send a ‘Start’ text via the serial monitor; 5. Wait until the sensor allows input pin 9 to pull-up to a HIGH level; 6. Send a ‘Completed’ text to the serial monitor.

My problem is that it doesn’t always work as anticipated. Sometimes the conversion delay is a textbook 50ms for 9-bit resolution, sometimes it indicates 0ms (clearly erroneous). An example serial monitor output is shown below.

19:19:33.802 → start 53ms conversion time as expected
19:19:33.855 → Completed
19:19:34.857 → ***************************************
19:19:35.058 → start Notice the start and completed timestamps are identical, even though
19:19:35.058 → Completed there’s a 25ms delay after the ‘Start’ text???
19:19:36.062 → ***************************************
19:19:36.309 → start 0ms again
19:19:36.309 → Completed
19:19:37.312 → ***************************************
19:19:37.512 → start A very normal 53ms
19:19:37.565 → Completed
19:19:38.569 → ***************************************
19:19:38.769 → start 0ms
19:19:38.769 → Completed
19:19:39.819 → ***************************************
19:19:40.019 → start 0ms
19:19:40.019 → Completed
19:19:41.022 → ***************************************
19:19:41.223 → start 54ms
19:19:41.277 → Completed
19:19:42.281 → ***************************************
19:19:42.483 → start 47ms
19:19:42.530 → Completed
19:19:43.488 → ***************************************
I have tried adding delays of 25ms, 50ms, and 100ms after the reset and SkipRom commands, thinking it may be that the DS18B20 data line needs time to settle, but the erroneous results continue.
Where am I going wrong?
The purpose of all this is to educate myself first, and subsequently to write ‘A guide to OneWire and DallasTemperature libraries - written by a beginner for beginners’.
The code is below, simplified to test only the 9 bit resolution setting.

Kind regards, GM

/* Reports the time when a 'convert' command 0x44 is sent to a single
 * sensor and reports the time when conversion is complete.
   
   DS18B20 Pinout (Left to Right, pins down, flat side toward you)
  - Left   = Ground
  - Center = Signal (Pin 10):  (with 3.3K to 4.7K resistor to +5 or 3.3 )
  - Right  = +5 or +3.3 V
   
*/

/*-----( Import needed libraries )-----*/
// Get 1-wire Library here: http://www.pjrc.com/teensy/td_libs_OneWire.html
#include <OneWire.h>

//Get DallasTemperature Library here:  http://milesburton.com/Main_Page?title=Dallas_Temperature_Control_Library
#include <DallasTemperature.h>
// Wire (I2C) Library
#include <Wire.h>

/*-----( Declare Constants and Pin Numbers )-----*/
// Data wire is plugged into pin D9 on the Arduino (can be changed)
#define ONE_WIRE_BUS 9    // NOTE: No ";" on #define  

/*-----( Declare objects )-----*/
// Setup a oneWire instance to communicate with any OneWire devices 
// (not just Maxim/Dallas temperature ICs)
OneWire bus(ONE_WIRE_BUS);

// Pass address of our oneWire instance to Dallas Temperature. 
DallasTemperature sensors(&bus);  //bloody pointers!

/*-----( Declare Variables )-----*/
// Assign the addresses of your 1-Wire temp sensors.
// See the tutorial on how to obtain these addresses:
// https://arduinoinfo.mywikis.net/wiki/Brick-Temperature-DS18B20#Read%20individual

DeviceAddress Tester = { 0x28, 0xB0, 0x92, 0x79, 0xA2, 0x01, 0x03, 0x89 }; // Labelled Sensor 7 (encapsulated)


void setup()   /****** SETUP: RUNS ONCE ******/
{
//------- Initialize the Temperature measurement library--------------
  Serial.begin(38400);

}//--(end setup )---



void loop()   /****** LOOP: RUNS CONSTANTLY ******/
{
  sensors.setResolution(Tester, 9);
  timeForConversion();

 // sensors.setResolution(Tester, 10);
 // timeForConversion();

 // sensors.setResolution(Tester, 11);
 // timeForConversion();

 // sensors.setResolution(Tester, 12);
 // timeForConversion();

  Serial.println ("***************************************");

}//--(end main loop )---
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*-----( Declare User-written Functions )-----*/
void timeForConversion()
  {
    bus.reset();        //reset the one wire bus. All devices wait for a command
    delay (100);
    
    bus.write(0xCC);          //Skip ROM command. Next command applies to all devices
    delay (100);
    
    bus.write(0x44);          //tell the sensor to convert the temperature
    
    Serial.println ("start");    //and record the start time via serial monitor
    delay (25);                //it always takes longer than this, and this delay
                              //allows bus to settle before testing for completion.

    while (!digitalRead (9))  //keep testing until conversion is done...
    {                          //because pin 9 will be pulled low until it's done
    }

    Serial.println ("Completed");   //record the end of conversion time.
    delay (1000);                  //a 1 second delay before the next conversion
  }

Don' trust the time values shown in the Serial monitor for timing things. Use millis() or micros() in your code.

The issue with your code is probably that Serial.print will not always print immediately, because Serial is asynchronous. If you want your code to wait the end of the print before doing something else, you have to use Serial.flush after each Serial.print

@guix, I have re-written the timeForConversion() function to use millis() as you suggested (new code posted below), with no improvement. My result always seems to be the value of the delay() just after ‘bus.write (0x44);’ no matter what conversion resolution I select, and the data sheet tells me that a 12 bit conversion takes in the order of 750 ms to complete.

Clearly my understanding of, or use of, the DS18B20 commands is wrong.
Please indicate how I can correct my mistake(s).

Regards, GM

/* Reports the time when a 'convert' command 0x44 is sent to a single
 * sensor and reports the time when conversion is complete.
   
   DS18B20 Pinout (Left to Right, pins down, flat side toward you)
  - Left   = Ground
  - Center = Signal (Pin 10):  (with 3.3K to 4.7K resistor to +5 or 3.3 )
  - Right  = +5 or +3.3 V
   
*/

/*-----( Import needed libraries )-----*/
// Get 1-wire Library here: http://www.pjrc.com/teensy/td_libs_OneWire.html
#include <OneWire.h>

//Get DallasTemperature Library here:  http://milesburton.com/Main_Page?title=Dallas_Temperature_Control_Library
#include <DallasTemperature.h>
// Wire (I2C) Library
#include <Wire.h>

/*-----( Declare Constants and Pin Numbers )-----*/
// Data wire is plugged into pin D9 on the Arduino (can be changed)
#define ONE_WIRE_BUS 9    // NOTE: No ";" on #define  

/*-----( Declare objects )-----*/
// Setup a oneWire instance to communicate with any OneWire devices 
// (not just Maxim/Dallas temperature ICs)
OneWire bus(ONE_WIRE_BUS);

// Pass address of our oneWire instance to Dallas Temperature. 
DallasTemperature sensors(&bus);

/*-----( Declare Variables )-----*/
// Assign the addresses of your 1-Wire temp sensors.
// See the tutorial on how to obtain these addresses:
// https://arduinoinfo.mywikis.net/wiki/Brick-Temperature-DS18B20#Read%20individual

//DeviceAddress Tester = { 0x28, 0xB0, 0x92, 0x79, 0xA2, 0x01, 0x03, 0x89 }; // Labelled Sensor 7 (encapsulated)
DeviceAddress Tester = { 0x28, 0x9B, 0xB4, 0x07, 0xD6, 0x01, 0x3C, 0x90 };

long millisStart = 0;
long millisEnd = 0;
int conversionTime;



void setup()   /****** SETUP: RUNS ONCE ******/
{
//------- Initialize the Temperature measurement library--------------
  Serial.begin(38400);

}//--(end setup )---



void loop()   /****** LOOP: RUNS CONSTANTLY ******/
{
  sensors.setResolution(Tester, 9);
  delay (20);
  timeForConversion();

  sensors.setResolution(Tester, 10);
  timeForConversion();

  sensors.setResolution(Tester, 11);
  timeForConversion();

  sensors.setResolution(Tester, 12);
  timeForConversion();

  Serial.println ("***************************************");

}//--(end main loop )---
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*-----( Declare User-written Functions )-----*/
void timeForConversion()
  {
    bus.reset();        //reset the one wire bus. All devices wait for a command
    delay (100);
    
    bus.write(0xCC);          //Skip ROM command. Next command applies to all devices
    delay (100);
    
    millisStart = millis();
    bus.write(0x44);          //tell the sensor to convert the temperature
    
    delay (5);                //it always takes longer than this, and this delay
                              //allows bus to settle before testing for completion.

    while (!digitalRead (ONE_WIRE_BUS))  //keep testing until conversion is done...
    {                          //because pin 9 will be pulled low until it's done
     Serial.println ("...waiting for conversion..."); 
    }

    millisEnd = millis();
    conversionTime = millisEnd - millisStart;
    Serial.print ("Conversion time = ");
    Serial.println (conversionTime);
    Serial.print (millisStart);
    Serial.print ("  ");
    Serial.println (millisEnd);
    delay (1000);                  //a 1 second delay before the next conversion
  }

What problem are you trying to solve here? Just use the datasheet values for conversion time as a function of resolution and you'll be fine. Your code can do useful work (update displays, respond to user input, etc) during that time if you write it in a non-blocking manner.

If speedy temperature measurements are your goal, then you've picked the wrong sensor.

I think I can answer my own question here:
From the DS18B20 datasheet, once a Convert T command is issued (0x44), I had erroneously assumed that the DS18B20 would HOLD the data line low until conversion was complete: this must be silly because if that were the case the OneWire bus would be paralysed until this one action had been completed on this one device.

Would I be right in concluding that I must issue a series of 'read' commands to this specific device, and base my decisions (the 'while' loop) on the data received in response? These are called read timeslots in the datasheet, although I have yet to figure out the command to send which means 'read timeslot'.

I'll keep reading; in the meantime I'd appreciate indications that I'm on the right track...

Regards, GM

gfvalvo:
What problem are you trying to solve here? Just use the datasheet values for conversion time as a function of resolution and you'll be fine. Your code can do useful work (update displays, respond to user input, etc) during that time if you write it in a non-blocking manner.

If speedy temperature measurements are your goal, then you've picked the wrong sensor.

Thank you for your reply.
The code I have written has little practical use except to demonstrate to myself that I can follow and understand the trail generated when I use someone else's library. It is a self-education exercise, but one that will be put to good use in a piece of practical code I am developing (for use within a domestic heating environment). This heating code will be writtten without use of blocking commands.

Fair enough. Enjoy the learning exercise.

The DS18B20 should work well with your heating system plans. I'd use simple bang-bang control with some hysteresis around the set point (aka like an HVAC system's thermostat). I'd be hesitant to try closed-loop (PID) control. With such a large delay through the sensor, I'd be worried that the phase shift could line up to cause loop instability.

"If the DS18B20 is powered by an external supply, the master can issue read time slots after the Convert T command and the DS18B20 will respond by transmitting a 0 while the temperature conversion is in progress and a 1 when the conversion is done."

You start a 'Read' time slot by setting the OneWire pin to OUTPUT and LOW for at least one microsecond and then change it to an INPUT pin. About 10 microseconds after you pulled the pin LOW, read the OneWire pin. It will read LOW if the conversion is still going and HIGH if the conversion is done. The DS18B20 releases the line 15 microseconds after the line was pulled LOW.

In code that looks something like:

boolean Busy() // true = conversion in progress
{
  pinMode(ONE_WIRE_BUS, OUTPUT);
  digitalWrite(ONE_WIRE_BUS, LOW);
  // Probably more than enough delay just calling funcitons
  pinMode(ONE_WIRE_BUS, INPUT);
  // Probably more than enough delay just calling funcitons
  int status = digitalRead(ONE_WIRE_BUS);
  return status == LOW;
}

If that doesn't work you may have to switch to direct port manipulation to reduce the time spent in function calls and then add no-op assembler instructions into the code to get the timing right.

The DallasTemperature library uses set conversion times based on the resolution:

// returns number of milliseconds to wait till conversion is complete (based on IC datasheet)
int16_t DallasTemperature::millisToWaitForConversion(uint8_t bitResolution) {

 switch (bitResolution) {
 case 9:
 return 94;
 case 10:
 return 188;
 case 11:
 return 375;
 default:
 return 750;
 }
}

The example code for asynchronous temperature readings uses an equation, which would be better if resolution is known at compile time, since the compiler can calculate the constant value.

  delayInMillis = 750 / (1 << (12 - resolution));

No need to keep reading the sensor to see if the conversion is complete, save the current value of millis() when you initiate the temperature reading, then read the temperature when the required amount of time has elapsed, initiating the next temperature reading immediately afterwards if you want the fastest continuous readings.

My Approach to evaluate conversion time of DS18B20 sensor for 9/10/11/12-bit resolution is as follows:

1. Table of Fig-1 showing bit values for resolution setting and maximum (theoretical) conversion time.


Figure-1:

2. The procedures:
(1) Set Resolution to 9-bit, (and then for 10-bit, 11-bit, and 12-bit)
(2) start DS18B20's conversion,
(3) start millis() based Timer to store conversion time,
(4) poll status word to see the end of conversion,
(5) read current time of millis Timer,
(6) read scratchpad memory of DS18B20 for temp and resolution bits,
(7) Show conversion time on Serial Monitor,

** **(8)** **
Show temperature value Serial Monitor.
(9) Show resolution bits on Serial Monitor. 00 = 9-bit, 01 = 10-bit, 10 = 11-bit 11 = 12-bit

3. Convert the tasks of Step-2 into following sketch (for 9/10/11/12-bit resolution) and upload into UNO.

#include<OneWire.h>
OneWire ds(10);
byte addr1[8];  //to hold 64-bit ROM Codes of DS18B20
byte data[9];   //buffer to hold data coming from Scratchpad memory of DS18B20
float celsius;
byte dsRes[] = {0x00, 0x00, 0x1F};//, 0x1F(0 R1 R0 11111)/0x3F/0x5F/0x7F for 9-10-11-12-bit Resolutio
byte busStatus;

void setup()
{
  Serial.begin(9600);
  //---------------------
  ds.reset();
  ds.search(addr1);  //collect 64-bit ROM code from sensor (DS1)t
}

void loop()
{
  measTemp();  //measure temp given by DS18B20 sensor
  delay(2000);
}

void measTemp()
{
  ds.write(0x4E);
  ds.write_bytes(dsRes, 3);  //set resolution bit
  //----------------------------------------------
  ds.reset();       //bring 1-Wire into idle state
  ds.select(addr1); //slect with DS-1 with address addr1
  ds.write(0x44);    //conversion command
  unsigned long prMillis = millis();
  do  //keep reading the ststus word until conversion is complete
  {
    busStatus = ds.read();  //keep reading until conversion is done
  }
  while (busStatus != 0xFF); //busStatus = 0xFF means conversion done
  //---------------------------
  Serial.print("Conversion time: "); 
  Serial.print(millis() - prMillis); Serial.println(" ms");
  ds.reset();
  ds.select(addr1);  //selectimg the desired DS18B20
  ds.write(0xBE);    //Function command to read Scratchpad Memory (9Byte)
  ds.read_bytes(data, 9); //data comes from DS and are saved into buffer data[8]
  //------------------------------------------------------------
  unsigned int rawTemp = (int)(data[1] << 4); //upper 4-bit of integer part of temperature
  byte n = data[0];   //n = lower 4-bit of integer part and 4-bit fractional part of temperature
  data[0] = data[0] >> 4; //lower 4-bit integer part of temperature
  rawTemp = rawTemp | (int)data[0];  //8-bit (including sign bit) integer part of temperature
  celsius = (float)rawTemp + 0.5 * bitRead(n, 3) + 0.25 * bitRead(n, 2) + 0.125
            * bitRead(n, 1) + 0.0625 * bitRead(n, 0); //temp in float format
  Serial.print("Temperature: ");
  Serial.print(celsius, 1); Serial.println(" degC"); //4, 3, 2, 1----> 12/11/10/9 bit resolution
  //----------------------------------------------------------------------
  Serial.print("Reading Resolution Bits from DS18B20: ");
  byte x = data[4]; //print res bits:0 R1 R0 11111;R1R0=00/01/10/11 -->9/10/11/12 bits reso
  for (int i = 6; i >= 5; i--)
  {
    Serial.print(bitRead(x, i), BIN);
  }
  Serial.println();
  Serial.print("================================================");
  Serial.println();
}

4. The Serial Monitor shows:
(1) for 9-bit resolution (Fig-2):
sm9-bitRes.png
Fig-2:

(1) for 10-bit resolution (Fig-3):
Sm10-bitRes.png
Fig-3:

(1) for 11-bit resolution (Fig-4):
sm11-bitRes.png
Fig-4:

(1) for 12-bit resolution (Fig-5):
sm12-bitRes.png
Fig-5:

5. Repeat Step-2, 3, 4 for 10-bit, 11-bit, and 12-bit resolution.

6. Conversion Time Summary
Resolution Data Sheet Actual Measured
9 max: 93.75 ms 52 ms
10 max: 187.5 ms 105 ms
11 max: 375 ms 207 ms
12 max: 750 ms 413 ms

sm11-bitRes.png

sm12-bitRes.png

sm9-bitRes.png

Sm10-bitRes.png

sm11-bitRes.png

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.