retorte.ch

Arduino Temperature Sensor

Introduction
Hardware
Software
Assembly
Operation
Discussion
License

Keywords: Arduino, Temperature sensor, Linux, Measurements, Perl, Dallas DS18B20, Maxim DS18B20, Periodic measurement, Prototype, 4.7k Resistor, Cronjob, Plotting, Chart

Introduction

To measure and collect room temperature data samples with a computer, there exists a broad range of commercial (proprietary) temperature collector appliances to be connected via USB. For such a trivial task the way to go certainly is to do it with things found in the toolbox at home. This is a short description of an Arduino-based temperature sensor which can be polled from a Gnu/Linux computer in any desired way.

A temperature chart

Hardware

This solution relies on the Arduino as host platform and the popular Maxim DS18B20 as sensor. It is also possible to solder the temperature sensor directly to a COM port of a computer, but with the Arduino as host it is not only possible to create an integrated appliance which easily can be taken to another computer or any place but also to let it independently collect data and read it out at a later time. This example implements only a simple temperature readout however.

Arduino

Arduino
Image source: www.arduino.cc

The Arduino (Article about Arduino on Wikipedia) is an easy and convenient microcontroller-based prototyping platform. It can be flashed with the program code and then runs stand-alone.

In Switzerland they can be mail-ordered in the dshop in Berne.

Temperature Sensor: Dallas/Maxim DS18B20

Maxim DS18B20 datasheet
Image source: From the Maxim DS18B20 datasheet

The Maxim (formerly Dallas) DS18B20 is a cheap, reliable and versatile digital temperature sensor. It is sufficently precise and can be addressed easily with the Arduino.

Maxim DS18B20 data website: www.maxim-ic.com/quick_view2.cfm?qv_pk=2812
Maxim DS18B20 datasheet: datasheets.maxim-ic.com/en/ds/DS18B20.pdf

In Switzerland it can be aquired in the legendary Zurich based electronics shop Pusterla or in one of the big electronic mail order companies.

Other Stuff

Additionally, for the prototype you need:

  • A 4.7 k resistor
  • Some wires
  • A breadboard

Software

We need two different pieces of software for the prototype. The software which runs on the Arduino is the one who communicates with the temperature sensor, smoothes the data and answers calls from the computer via USB. The one running on the computer fetches the data from the Arduino.

On Arduino: Driver for Sensor/Aggregator

The Arduino code is written in the Arduino Programming Language (no, really?! ;)). It relies on the OneWire library to communicate with the sensor. In order to use the below code, you need to download the library and put it into the hardware/libraries directory of your Arduino IDE first. The parts used to talk to the sensor are taken from the Arduino OneWire example page.

Arduino interface playground: www.arduino.cc/playground/Main/InterfacingWithHardware
Arduino OneWire example page: www.arduino.cc/playground/Learning/OneWire
OneWire library: milesburton.com/index.php?title=Dallas_Temperature_Control_Library

Download the source code here: arduino_temperature_sensor.pde

Have a look at it directly here:

// Include library found here:
// http://milesburton.com/index.php?title=Dallas_Temperature_Control_Library
#include <OneWire.h>

// DS18S20 Temperature chip i/o
OneWire ds(8);  // on pin 8

int ledPin =  13;    // LED connected to digital pin 13

  // Do configuration
  
  // The buffer window size determines how many single temperature data samples

  // are collected and held at the same time. The program takes the average of 
  // all these samples. With the standard values for windowSize (6) and
  // fetchInterval (10000) there are six samples collected in a minute. A read-
  // out thus gives the average over a minute.
  //
  // Standard: 6

  int const windowSize = 6;

	// Time between temperature fetches
	// 

	// Standard: 10 seconds
  unsigned long fetchInterval = 10000;
  
  // Frequency of status blinker

  // Has nothing to do with the temperature stuff, but indicates that the 
  // arduino is doing something
  //
  // Standard: 1 second
  unsigned long blinkInterval = 1000;

  
  
  // Initialize variables
  int toggle = 1;
  double currentTemp, temperature;

  unsigned long lastBlinkTime;
  unsigned long lastFetchTime;
  double tempArray[windowSize];

  int first;

void setup(void) {

  // Initialize inputs/outputs
  // Start serial port
  Serial.begin(38400);
  delay(2000);

  
  // Initialize the digital pin as an output:
  pinMode(ledPin, OUTPUT);     
  
  // At startup, fill array with current temperature
  currentTemp = fetchTemp();

  for(int i = 0; i < windowSize; i++) {

        shift_in(currentTemp, tempArray, windowSize);
  }
  
  temperature = currentTemp;

}

// Main loop
void loop(void) {

  // If there has been passed a certain time

  if (millis() - lastFetchTime > fetchInterval) {
  
    // save the last time

    lastFetchTime = millis();   

    // Fetch current temp
    currentTemp = fetchTemp();

    // Shift in current temperature into temperature array
    shift_in(currentTemp, tempArray, windowSize);
    
    // Determine averaged temperature

    temperature = mean(tempArray, windowSize);
  }
  
    // Do some blinking every time

    if (millis() - lastBlinkTime > blinkInterval) {
      // save the last time

      lastBlinkTime = millis();   
  
      if(toggle == 1) {

        digitalWrite(ledPin, LOW);
        toggle = 0;
      }

      else {
        digitalWrite(ledPin, HIGH);
        toggle = 1;

      }
    }
  
  // Send data only when you receive data:
  if (Serial.available() > 0) {

        while(Serial.available()) {
          Serial.flush();

        }

	// Say what you got to say:
	Serial.println(temperature);
	}

}

// Calculates the mean of all items in the array
double mean(double *a, double length) {

  double sum = 0;
  for(int i = 0; i < length; i++) {

    sum += a[i];
  }
  return (sum/length);

}

// Assigns the new item as first element of an array and shifts every other
// items one position to the end. The last item is discarded
void shift_in(double newItem, double *a, double length) {

  for(int i = length-2; 0 <= i ; i--) {

    a[i+1] = a[i];
  }

  a[0] = newItem;
}

// The self-contained fetch temp function.
// Most of the code was taken from the example code of this page:

// http://www.arduino.cc/playground/Learning/OneWire
double fetchTemp(void) {
  
  byte i;

  byte present = 0;
  byte data[12];

  byte addr[8];

	// Some test routines
  if ( !ds.search(addr)) {

       ds.reset_search();
  }

  if ( OneWire::crc8( addr, 7) != addr[7]) {

      Serial.print("CRC is not valid!\n");
  }

  if ( addr[0] != 0x28) {

      Serial.print("Device is not a DS18B20 family device.\n");
  }

  ds.reset();

  ds.select(addr);
  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);    
  ds.write(0xBE);         // Read Scratchpad

  
  for ( i = 0; i < 9; i++) {           // we need 9 bytes

    data[i] = ds.read();
    
    // Debug output:
   	//Serial.print(data[i], HEX);

    //Serial.print(" ");
  }
  
  int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

  double result;
  
  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;

  result = Whole;
  result += ((double)Fract/100);

  if(SignBit) {
    result *= -1;
  }

  
  return result;
}

On Linux: Perl Fetch Script

The Perl script on the computer connects to the USB port and fetches the current temperature from the Arduino. It either writes the temperature directly to the console or, if called with a filename as argument, appends a line with current timestamp and current temperature to the file.

Download the source code here: fetch_temperature.perl

Have a look at it directly here:

#!/usr/bin/perl
#
# Usage: fetch_temperature [outfile]
#

# Nice data fetch script for arduino temperature sensor
#
# If you call it without arguments, it prints the current temperature to the
# console.
#
# If you pass a filename as argument, it adds the current date, time and temperature
# to the end of the file

use strict;

use warnings;

# Set up the serial port
use Device::SerialPort;
my $port = Device::SerialPort->new("/dev/ttyUSB0");

# Some simple error catch mechanism
if(!$port) {
	my $port = Device::SerialPort->new("/dev/ttyUSB1");

}

# If no connection could be made, quit
if(!$port) {
	print "No device found!!\n";

	exit 1;
}

# Initialize communication
# # 38400, 81N on the USB ftdi driver
$port->baudrate(38400);

$port->databits(8);
$port->parity("none");
$port->stopbits(1);

# Some initialization
my $filename = "";
$filename = shift;

# Create nice timestamp YYYY-MM-DD HH:MM:SS for file output
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);

my $timestamp = sprintf "%4d-%02d-%02d %02d:%02d:%02d",
$year+1900,$mon+1,$mday,$hour,$min,$sec;

my $temp = "";

my $starttime = time(); 

my $written = 0;

# Loop while there is no data available
while (!$temp) {

	# If we didn't send anything yet, send signal (a simple '1') to the arduino
	if(!$written) {	
		$port->write(1);

		$written = 1;
	}

	# Poll to see if any data is coming in
	$temp = $port->lookfor();

	
	# If there was too much time spent, quit the loop and set error message as result
	if(time() > $starttime + 5) {

		$temp = "Sensor not yet ready!";
	}
}

# If a filename has been passed as argument, write the result into this file
if($filename) {

	# Write down temp into file with current timestamp
	open FILE, ">>$filename" or die "could not open $filename: $!\n";

	print FILE "$timestamp $temp\n";
	close FILE;
}
# else print the result to the console

else {
	print "$temp\n";
}

# goodbye

Assembly

After you uploaded the Arduino code to the Arduino, you need to wire the Arduino with the temperature sensor according to the sensors data sheet:

Maxim DS18B20 wiring schema
Image source: From the Maxim DS18B20 datasheet

Here are pictures of how my prototype was wired:

Arduino wiring

Breadboard wiring

Operation

The operation is now pretty straight-forward: Plug the Arduino in to the computer with a USB cable and let it initialize, which takes around a second. It is then ready to provide temperature data samples.
(Certainly it is assumed that your system is capable of USB communication and has all drivers, modules and other mumbo jumbo (in particular the Perl interpreter and the libdevice-serialport-perl library!) installed!)

Fetching Temperature

Place the Perl script in a directory and make it executable with this command:

user@localhost $ chmod +x fetch_temperature.perl
You can then call it to fetch a temperature sample:
user@localhost $ ./fetch_temperature.perl
19.96
user@localhost $

To let it write the temperature with a timestamp at the end of a file, call it with a filename as argument. This mode is very useful if you want it to be run as cronjob.

user@localhost $ ./fetch_temperature.perl temp_data.txt
user@localhost $
After some calls, the file temp_data.txt may look like this. The schema is "YYYY-MM-DD HH:MM:SS TEMP":
...
2010-01-05 18:00:00 20.12
2010-01-05 18:15:00 20.29
2010-01-05 18:30:01 20.43
2010-01-05 18:45:00 20.40
...
Note that the sensor only has a precision of around 0.1°C and an accuracy of +-0.5°C. Because of the aggregation of the last six samples the output seems to be of higher precision.

Plotting Temperature

A temperature chart

With the data in such format at hand it is now pretty easy to plot it with any tool. The plot above was done with the URL-based Google Charts API (code.google.com/apis/chart/) which is convenient for small and simple charts. It has this 'source code' (which can be copied into the address bar of a web browser):

http://chart.apis.google.com/chart?
chs=720x280
&cht=lc
&chds=12,36
&chxt=x,y
&chdl=Temperature%20Outside%20[C]
&chdlp=b

&chg=16.67,0,1,4
&chts=000000,24
&chxl=0:|00|04|08|12|16|20|24|1:|12|16|20|24|28|32|36
&chco=275f83
&chm=B,A2D3F3,0,1,0|B,A2D3F3,1,2,0
&chd=t:16.9,16.5,16.3,16.4,16.3,16.0,15.7,15.4,15.3,15.4,16.7,18.4,18.5,18.1,18.9,20.2,21.7,22.3,23.4,24.5,28.1,27.1,27.3,29.4,29.2,28.9,30.3,31.4,31.8,30.1,27.3,25.6,24.0,23.0,22.1,21.1

For more complex visualisations there are many other tools. Here a few:

Discussion

The prototype was done pretty fast and works satisfactory and with good precision. For a definitive solution there must be taken care of how the sensor is deployed. The naked sensor is very sensitive, it reacts very fast if e.g. a person places itself in its surrounding. It thus either has to be shielded properly with one or maybe two layers of something like airtight white capsules which constrain the heat transfer and stabilize the measurements, or simply more single data samples have to be taken in a longer time period to smoothen the curve.

It also is obvious that this version of the Arduino acts definitively more as a prototyping platform than a deploying platform since in the standard version it offers no means for a durable connection of the wires. However this could be dealt with so-called 'shields'; specialized backpacks who offer additional functionality. There are also SMD versions of the Arduino offered (called 'Arduino Pro'). Those are better suited for building ruggedized appliances and such.

License

The source code of the project is published under the GPL - GNU Public License.

top