Temperature unit

Coordinator
Aug 30, 2010 at 7:34 PM
Edited Aug 31, 2010 at 12:59 PM

http://datasheets.maxim-ic.com/en/ds/DS18S20.pdf 

 

<object width="400" height="225"> <param name="allowfullscreen" value="true" /> <param name="allowscriptaccess" value="always" /> <param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=4370075&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00adef&fullscreen=1&autoplay=0&loop=0" /><embed type="application/x-shockwave-flash" width="400" height="225" src="http://vimeo.com/moogaloop.swf?clip_id=4370075&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00adef&fullscreen=1&autoplay=0&loop=0" allowfullscreen="true" allowscriptaccess="always"></embed> </object>

Coordinator
Aug 30, 2010 at 7:36 PM

Temperature Measurment using the Dallas DS18B20

 

copyright, Peter H Anderson, Baltimore, MD, May 6, '07

 


Introduction

Note that this is my first venture using the Arduino and I am amazed with the possibilities. I am also amazed with the amount of dedicated talent that has gone into the Arduino effort.

I used Paul Badger's Board and a FTDI TTL-232R USB-to-TTL serial cable.

This program continually measures the temperature at three points using the Dallas DS18B20 1-W thermometer. Note that each device is on its own dedicated pair (DQ and GRD). One might use this to monitor outdoor, indoor and the temperature in the wine cellar. My own experience has been to limited any run to 200 feet.

My plan is to extend this to illustrate how to interface with the DS2438 battery monitor to measure a voltage and a temperature, the DS2423 which has two 32 bit counters and the DS2450 quad A/D converter. I also plan to develop code for a PC to periodically perform measurements and log data or operate a relay or similar to turn on a fan or heater.

I realize there is a library of OneWire routines. However, in the interest of learning I wrote my own OneWireReset, OneWireOutByte and OneWireInByte functions.

Detailed Discussion.

Each transmission is initiated with a reset (or presence) pulse, by bringing the signal lead low for 500 us and then back to high-Z for 500 us.

The commands 0xcc (skip ROM) followed by 0x44 (perform temperature measurement) are written to the device. Note that when using the DS18B20 in the parasite mode, a strong source of +5 VDC must exerted within about 10 us of issuing the perform temperature measurement command. Thus, I opted to implement the strong pull-up in the OneWireOutByte function.

This is followed by the reset pulse and commands 0xcc (skip ROM) and 0xbe (read temperature).

Two byte are then read into variables LowByte and HighByte. The bytes are then combined into a 16-bit quantity TReading. This is a 16-bit signed number which is converted to temperature by multiplying by 0.0625.

However, rather than use floats, 100 times the Tc was calculated and then broken into the whole and fractional parts when displayed.

Note that the serial LCD used to display the result is my LCD #117 Serial LCD.

 


 

// DS18B20_1 - Arduino
//
// Continually measures temperatures at three points using the Dallas DS18B20 on three
// separate Arduino pins.
//
// Uses the parasitic power mode
//
// Displays to a serial LCD operating at 9600 baud.
//
// Arduino Board            DS18B20
//
//                    +5 VDC
//                    |
//                   4.7K
// 8 ---------------- |----- 2 (DQ)  Note that terms 1 and 3 are grounded.
//
// 7 ---- same as above
// 6 ---- same as above
//
// Tx ---------------------------- To Serial LCD (LCD #117)
//
// copyright, Peter H Anderson, Baltimore, MD, May 5, '07


void setup()
{
  int n, dev_channel[3] = {8, 7, 6}, _1W_Pin;

  for (n=0; n<1; n++)
  {
    _1W_Pin = dev_channel[n];
    digitalWrite(_1W_Pin, LOW);
    pinMode(_1W_Pin, INPUT);      // sets the digital pin as input (logic 1)
  }
  Serial.begin(9600);
  delay(100);
  Serial.print("?B40"); // set backlight intensity
  delay(100);
}

void loop()
{
  int n, dev_channel[3] = {8, 7, 6}, _1W_Pin;
  int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

  for (n=0; n<3; n++)
  {
     _1W_Pin = dev_channel[n];
     OneWireReset(_1W_Pin);
     OneWireOutByte(_1W_Pin, 0xcc, 0);
     OneWireOutByte(_1W_Pin, 0x44, 1); // perform temperature conversion, strong pullup for one sec

     OneWireReset(_1W_Pin);
     OneWireOutByte(_1W_Pin, 0xcc, 0);
     OneWireOutByte(_1W_Pin, 0xbe, 0);

     LowByte = OneWireInByte(_1W_Pin);
     HighByte = OneWireInByte(_1W_Pin);
     TReading = (HighByte << 8) + LowByte;
     SignBit = TReading & 0x8000;  // test most sig bit
     if (SignBit) // negative
     {
         TReading = (TReading ^ 0xffff) + 1; // 2's comp
     }
     Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

     Whole = Tc_100 / 100;  // separate off the whole and fractional portions
     Fract = Tc_100 % 100;

     if (n==0)  // if its the first time, clear the LCD
     {
         Serial.print("?f");
         delay(100);
     }

     if (SignBit) // If its negative
     {
        Serial.print("-");
     }
     Serial.print(Whole);
     Serial.print(".");
     if (Fract < 10)
     {
        Serial.print("0");
     }
     Serial.print(Fract);
     Serial.print("?n");
  }
  delay(5000);      // 5 second delay.  Adjust as necessary
}

void OneWireReset(int _1W_Pin) // reset.  Should improve to act as a presence pulse
{
     digitalWrite(_1W_Pin, LOW);
     pinMode(_1W_Pin, OUTPUT); // bring low for 500 us
     delayMicroseconds(500);
     pinMode(_1W_Pin, INPUT);
     delayMicroseconds(500);
}

void OneWireOutByte(int _1W_Pin, byte d, byte strong) // output byte d (least sig bit first).
{
   byte n;

   for(n=8; n!=0; n--)
   {
      if ((d & 0x01) == 1)  // test least sig bit
      {
         digitalWrite(_1W_Pin, LOW);
         pinMode(_1W_Pin, OUTPUT);
         delayMicroseconds(5);
         pinMode(_1W_Pin, INPUT);
         delayMicroseconds(60);
      }
      else
      {
         digitalWrite(_1W_Pin, LOW);
         pinMode(_1W_Pin, OUTPUT);
         delayMicroseconds(60);
         pinMode(_1W_Pin, INPUT);
      }

      d=d>>1; // now the next bit is in the least sig bit position.
   }
   if(strong)
   {
       digitalWrite(_1W_Pin, HIGH); // One sec of strong +5 VDC
       pinMode(_1W_Pin, OUTPUT);
       delay(1000);
       pinMode(_1W_Pin, INPUT);
       digitalWrite(_1W_Pin, LOW);
   }
}

byte OneWireInByte(int _1W_Pin) // read byte, least sig byte first
{
    byte d, n, b;

    for (n=0; n<8; n++)
    {
        digitalWrite(_1W_Pin, LOW);
        pinMode(_1W_Pin, OUTPUT);
        delayMicroseconds(5);
        pinMode(_1W_Pin, INPUT);
        delayMicroseconds(5);
        b = digitalRead(_1W_Pin);
        delayMicroseconds(50);
        d = (d >> 1) | (b<<7); // shift d to right and insert b in most sig bit position
    }
    return(d);
}

Coordinator
Aug 31, 2010 at 12:59 PM

 

Dallas Semiconductor's 1-Wire Protocol

(This page may need splitting into a general guide to 1-Wire, and a separate page with the "using theDS1820" material. I would suggest that the general guide inherit the old links.)

 

Dallas Semiconductor (now Maxim) produces a family of devices that are controlled through a proprietary 1-wire protocol. There are no fees for programmers using the Dallas 1-Wire (trademark) drivers.

 

On a 1-Wire network, which Dallas has dubbed a "MicroLan" (trademark), a single "master" device communicates with one or more 1-Wire "slave" devices over a single data line, which can also be used to provide power to the slave devices. (Devices drawing power from the 1-wire bus are said to be operating in parasitic power mode.)

 

Tom Boyd's guide to 1-Wire may tell you more than you want to know... but it may also answer questions and inspire interest.

 

The 1-wire temperature sensors have become particularly popular, because they're inexpensive and easy to use, providing calibrated digital temperature readings directly. They are more tolerant of long wires between sensor and Arduino. The sample code below demonstrates how to interface with a 1-wire device using Jim Studt's OneWire Arduino library, with the DS18S20 digital thermometer as an example. Many 1-Wire chips can operate in both parasitic and normal power modes.

 

MicroLans can be accessed directly by an Arduino, using the mature Arduino 1-Wire library. Alternatively, they can be accessed through an interface which costs a little money, but reduces the workload inside the Arduino. The interface cost $8 in kit form at 2/2010. There is a guide to using itfrom an independent source.

 

Powering a DS18x20

The chip can be powered two ways. One (the "parasitic" option) means that only two wires need go to the chip. The other may, in some cases, give more reliable operation (parasitic often works well), as an extra wire carrying the power for the chip is involved. For getting started, especially if your chip is within 20 feet of your Arduino, the parasitic option is probably fine. The code below works for either option, anyway.

 

Parasite power mode
When operating in parasite power mode, only two wires are required: one data wire, and ground. At the master, a 4.7k pull-up resistor must be connected to the 1-wire bus. When the line is in a "high" state, the device pulls current to charge an internal capacitor.

 

This current is usually very small, but may go as high as 1.5 mA when doing a temperature conversion or writing EEPROM. When a slave device is performing one these operations, the bus master must keep the bus pulled high to provide power until the operation completes; a delay of 750ms is required for a DS18S20 temperature conversion. The master can't do anything during this time, like issuing commands to other devices, or polling for the slave's operation to be completed. To support this, theOneWire library makes it possible to have the bus held high after the data is written.

 

Normal (external supply) mode
With an external supply, three wires are required: the bus wire, ground, and power. The 4.7k pull-up resistor is still required on the bus wire. As the bus is free for data transfer, the microcontroller can continually poll the state of a device doing a conversion. This way, a conversion request can finish as soon as the device reports being done, as opposed to having to wait 750ms in "parasite" power mode.

 

Note on resistors: For larger networks, try something smaller. The ATmega328/168 datasheet indicates starting at 1k6 and a number of users have found smaller to work better on larger networks.

 

Addressing a DS18x20

Each 1-Wire device contains a unique 64-bit 'rom' code, consisting of an 8-bit family code, a 48-bit serial number, and an 8-bit CRC. The CRC is used to verify the integrity of the data. The sample code, below, ensures that the device being addressed is a DS18S20 by checking for its family code, 0x10. (To use the sample code with the newer DS18B20 sensor, you'd check for a family code of 0x28, instead, and for the DS1822 you'd check for 0x22.)

 

Before sending a command to a slave device, the master must first select that device using its rom code. You can also address a command to all slave devices by issuing a 'skip rom' command, instead. This is only safe for commands that don't elicit a response from the slave devices - data collision will occur if data is requested from more than one slave.

 

Please see the DS18S20 or DS18B20 datasheets for more detailed information.

 

Library

Derived from Jim Studts OneWire library (below) you can now control several Dallas ICs through a very simple library by Miles Burton. Dallas Temperature Control Library

 

For more generic purposes, Jim Studts library can be used to interface with various Dallas ICs.

 

Jim Studt created a OneWire library that makes it easy to work with 1-Wire devices. The original version only worked with arduino-007 and required a large (256-byte) lookup table to perform CRC calculations. This was later updated to work with arduino-0008 and later releases. The most recent version eliminates the CRC lookup table; it has been tested under arduino-0010.

 

The OneWire library has a bug causing an infinite loop when using the search function. Fixes to this can be found in this Arduino thread, see posts #3#17#24#27 for a variety of fixes.

 

Version 2.0 merges Robin James's improved search function and includes Paul Stoffregen's improved I/O routines (fixes occasional communication errors), and also has several small optimizations.

 

Example code

 

  1. #include <OneWire.h>
  2.  
  3. // DS18S20 Temperature chip i/o
  4. OneWire ds(10);  // on pin 10
  5.  
  6. void setup(void) {
  7.   // initialize inputs/outputs
  8.   // start serial port
  9.   Serial.begin(9600);
  10. }
  11.  
  12. void loop(void) {
  13.   byte i;
  14.   byte present = 0;
  15.   byte data[12];
  16.   byte addr[8];
  17.  
  18.   if ( !ds.search(addr)) {
  19.       Serial.print("No more addresses.\n");
  20.       ds.reset_search();
  21.       return;
  22.   }
  23.  
  24.   Serial.print("R=");
  25.   for( i = 0; i < 8; i++) {
  26.     Serial.print(addr[i], HEX);
  27.     Serial.print(" ");
  28.   }
  29.  
  30.   if ( OneWire::crc8( addr, 7) != addr[7]) {
  31.       Serial.print("CRC is not valid!\n");
  32.       return;
  33.   }
  34.  
  35.   if ( addr[0] != 0x10) {
  36.       Serial.print("Device is not a DS18S20 family device.\n");
  37.       return;
  38.   }
  39.  
  40.   ds.reset();
  41.   ds.select(addr);
  42.   ds.write(0x44,1);         // start conversion, with parasite power on at the end
  43.  
  44.   delay(1000);     // maybe 750ms is enough, maybe not
  45.   // we might do a ds.depower() here, but the reset will take care of it.
  46.  
  47.   present = ds.reset();
  48.   ds.select(addr);    
  49.   ds.write(0xBE);         // Read Scratchpad
  50.  
  51.   Serial.print("P=");
  52.   Serial.print(present,HEX);
  53.   Serial.print(" ");
  54.   for ( i = 0; i < 9; i++) {           // we need 9 bytes
  55.     data[i] = ds.read();
  56.     Serial.print(data[i], HEX);
  57.     Serial.print(" ");
  58.   }
  59.   Serial.print(" CRC=");
  60.   Serial.print( OneWire::crc8( data, 8), HEX);
  61.   Serial.println();
  62. }

 

 

Converting HEX to something meaningful (Temperature)

In order to convert the HEX code to a temperature value, first you need to identify if you are using aDS18S20, or DS18B20 series sensor. The code to read the temperature needs to be slightly different for the DS18B20 (and DS1822), because it returns a 12-bit temperature value (0.0625 deg precision), while the DS18S20 and DS1820 return 9-bit values (0.5 deg precision).

 

First off, you need to define some variables, (put right under loop() above)

 

int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

 

Then for a DS18B20 series you will add the following code below the Serial.println(); above

 

 LowByte = data[0];
  HighByte = data[1];
  TReading = (HighByte << 8) + LowByte;
  SignBit = TReading & 0x8000;  // test most sig bit
  if (SignBit) // negative
  {
    TReading = (TReading ^ 0xffff) + 1; // 2's comp
  }
  Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

  Whole = Tc_100 / 100;  // separate off the whole and fractional portions
  Fract = Tc_100 % 100;


  if (SignBit) // If its negative
  {
     Serial.print("-");
  }
  Serial.print(Whole);
  Serial.print(".");
  if (Fract < 10)
  {
     Serial.print("0");
  }
  Serial.print(Fract);

  Serial.print("\n");

 

This block of code converts the temperature to deg C and prints it to the Serial output.

 

A Code Snippet for the DS 1820 with 0.5 Degree Resolution

Above example works only for the B-type of the DS1820. Here is a code example that works with the lower resolution DS1820 and with multiple sensors diplaying their values on a LCD. Example is working with Arduino pin 9. Feel free to change that to an appropriate pin for your use. Pin 1 and 3 of theDS1820 has to be put to ground! In the example a 5k resistor is put from pin 2 of DS1820 to Vcc (+5V). See LiquidCrystal documentation for connecting the LCD to the Arduino.

#include <OneWire.h>
#include <LiquidCrystal.h>
// LCD=======================================================
//initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
#define LCD_WIDTH 20
#define LCD_HEIGHT 2

/* DS18S20 Temperature chip i/o */

OneWire  ds(9);  // on pin 9
#define MAX_DS1820_SENSORS 2
byte addr[MAX_DS1820_SENSORS][8];
void setup(void) 
{
  lcd.begin(LCD_WIDTH, LCD_HEIGHT,1);
  lcd.setCursor(0,0);
  lcd.print("DS1820 Test");
  if (!ds.search(addr[0])) 
  {
    lcd.setCursor(0,0);
    lcd.print("No more addresses.");
    ds.reset_search();
    delay(250);
    return;
  }
  if ( !ds.search(addr[1])) 
  {
    lcd.setCursor(0,0);
    lcd.print("No more addresses.");
    ds.reset_search();
    delay(250);
    return;
  }
}
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
char buf[20];

void loop(void) 
{
  byte i, sensor;
  byte present = 0;
  byte data[12];

  for (sensor=0;sensor<MAX_DS1820_SENSORS;sensor++)
  {
    if ( OneWire::crc8( addr[sensor], 7) != addr[sensor][7]) 
    {
      lcd.setCursor(0,0);
      lcd.print("CRC is not valid");
      return;
    }

    if ( addr[sensor][0] != 0x10) 
    {
      lcd.setCursor(0,0);
      lcd.print("Device is not a DS18S20 family device.");
      return;
    }

    ds.reset();
    ds.select(addr[sensor]);
    ds.write(0x44,1);         // start conversion, with parasite power on at the end

    delay(1000);     // maybe 750ms is enough, maybe not
    // we might do a ds.depower() here, but the reset will take care of it.

    present = ds.reset();
    ds.select(addr[sensor]);    
    ds.write(0xBE);         // Read Scratchpad

    for ( i = 0; i < 9; i++) 
    {           // we need 9 bytes
      data[i] = ds.read();
    }

    LowByte = data[0];
    HighByte = data[1];
    TReading = (HighByte << 8) + LowByte;
    SignBit = TReading & 0x8000;  // test most sig bit
    if (SignBit) // negative
    {
      TReading = (TReading ^ 0xffff) + 1; // 2's comp
    }
    Tc_100 = (TReading*100/2);    

    Whole = Tc_100 / 100;  // separate off the whole and fractional portions
    Fract = Tc_100 % 100;

    sprintf(buf, "%d:%c%d.%d\337C     ",sensor,SignBit ? '-' : '+', Whole, Fract < 10 ? 0 : Fract);

    lcd.setCursor(0,sensor%LCD_HEIGHT);
    lcd.print(buf);
  }
}