Temperature logger

Monitor up to 7x DS18b20 1-wire devices using the http://www.atmel.com/dyn/resources/prod_documents/doc0839.pdf AT90S2313 AVR microcontroller. Each device will report its unique ROMID along with the temperature to the rs232 port whenever a change in temperature is detected.

2313 temperature board

Two DS18b20 shown, one directly attached to the PCB via a 3pin socket others can be connect via additional pins.

The current draw from this device is 25mA. This is too much to drive from the RS232 port however a USB port can drive up to 500mA at 5v. Perfect.

How to save current:

  • if we went for an interrupt driven approach rather then a tight loop we could idle down the chip in the meantime. However we are tight on code space. At IDLE the circuit still draws 17mA - so we may save up to 8mA using an interrupt driven approach.
  • Using a lower frequency chip say 4Mhz.

Alas its already built now.

2313 Temperature monitor
Found 2 1-wire devices
28A83FF6010000DB28 22
28EA31F60100006228 22
28A83FF6010000DB28 23
28A83FF6010000DB28 22

Reference:

2313temp.zip

'********************************************************************
'* 2313temp.bas
'*
'* Measure temperature using up to 4 1-wire 18b20 or 18s20 devices
'* BASCOM
'********************************************************************

$regfile = "2313def.dat"
$crystal = 10000000
$baud = 9600
 
Config 1wire = Pd.4
 
' DS18x20 ROM ID
Const Ds18s20_id = &H10
Const Ds18b20_id = &H28
 
' COMMANDS
Const Ds18x20_convert_t = &H44
Const Ds18x20_read = &HBE
 
Declare Sub Convallt                                        ' Convert T on ALL sensors
Declare Sub Meas_to_cel(id As Byte , Offset As Byte)
Declare Sub Monitor
Declare Sub Disp_temp(cnt As Byte , Offset As Byte)
 
' 1-wire devices
Const Max1wire = 7
' Up to 7 each having an 8 byte ROMID
Dim Dsid(56) As Byte                                        ' Dallas ID 64bit inc. CRC
Dim Dsvalue(max1wire) As Byte                               ' Value of each sensor
Dim Dssign As Byte                                          ' If max1wire > 7 make this a word
Dim Cnt1wire As Byte                                        ' Number of 1-wire devices found

Dim I As Byte
Dim J As Byte
Dim K As Byte
Dim Meas As Word
Dim Sc(9) As Byte                                           ' 1-wire scratch pad
'Sc(1) 'Temperature LSB
'Sc(2) 'Temperature MSB
'Sc(3) 'TH/user byte 1 also SRAM
'Sc(4) 'TL/user byte 2 also SRAM
'Sc(5) 'config  also SRAM x R1 R0 1 1 1 1 1 - the r1 r0 are config for resolution - write FF to byte for 12 bit
'Sc(6) 'res
'Sc(7) 'res
'Sc(8) 'res
'Sc(9) '8 CRC

'************* MAIN **************
Print "2313 Temperature monitor"
 
' Gather ROM ID for all 1-wire devices
Cnt1wire = 1wirecount()
Print "Found " ; Cnt1wire ; " 1-wire devices"
If Cnt1wire = 0 Then
  Idle
End If
 
If Cnt1wire > Max1wire Then
  Cnt1wire = Max1wire
End If
 
I = 1
Dsid(i) = 1wsearchfirst()
For J = 1 To Cnt1wire
  I = I + 8
  Dsid(i) = 1wsearchnext()
Next
 
Do
  Convallt
  Waitms 750
  Monitor
Loop
 
End
 
Sub Monitor
  J = 1
  For I = 1 To Cnt1wire
      1wverify Dsid(j)                                      'Issues the "Match ROM "
      If Err = 0 Then
        1wwrite Ds18x20_read
        Sc(1) = 1wread(9)
        If Sc(9) = Crc8(sc(1) , 8) Then
          Call Disp_temp(i , J)
        End If
      End If
 
    J = J + 8
  Next
End Sub
 
 
'Makes the Dallas "Convert T" command on the 1w-bus configured in "Config 1wire = Portb. "
'WAIT 200-750 ms after issued, internal conversion time for the sensor
'SKIPS ROM - so it makes the conversion on ALL sensors on the bus simultaniously
'When leaving this sub, NO sensor is selected, but ALL sensors has the actual
'temperature in their scratchpad ( within 750 ms )

Sub Convallt
 1wreset                                                    ' reset the bus
 1wwrite &HCC                                               ' skip rom
 1wwrite Ds18x20_convert_t
End Sub
 
 
' Store WHOLE degree's only
' Display if the value has changed since last polled.
Sub Disp_temp(cnt As Byte , Offset As Byte)
  Meas = 0
  Meas = Makeint(sc(1) , Sc(2))
 
  ' 18S20 is only 9bit upscale to 12bit
'  If Dsid(offset) = Ds18s20_id Then
'    Meas = Meas And &HFFFE
'    Shift Meas , Left , 3
'  End If

  If Meas.15 = 1 Then                                       ' if meas & &H8000 then
    Set Dssign.cnt
    ' convert to +ve, (two's complement)++
    Meas = Meas Xor &HFFFF
    Incr Meas
  Else
    Reset Dssign.cnt
  End If
 
  Shift Meas , Right , 4
  ' If this value different from the stored value ?
  If Dsvalue(cnt) <> Meas Then
    ' Dump ROMID of 1-wire device followed by temperature
    K = Offset + 8
    Do
      Print Hex(dsid(offset));
      Incr Offset
    Loop Until Offset > K
    Print " ";
    If Dssign.cnt = 1 Then
      Print "-";
    End If
    Print Meas
    Dsvalue(cnt) = Meas
  End If
 
End Sub

To read the device from a linux box we can use the following snippet of perl. Now that we have a data value in a file we can use this value to feed my rrdtools logging system. Why use an intermediate file? The temperature sensor only sends a value when it detects a change however the logging software wants a value on a periodic basis ;so it can poll this file.

I guess I could have changed the firmware to send a value every (x) mins, however if you want different polling frequencies then you now need to change the firmware to suit, which is more difficult than adjusting your CRON that reads this file!.

#!/usr/bin/perl
use Device::SerialPort;
 
# Map 1-wire ROMID to a location
%rommap=(
      "28EA31F60100006228" => "inside",
      "28A83FF6010000DB28" => "outside"
);
 
# setup SERIAL port
my $port = Device::SerialPort->new("/dev/ttyS0") || die "Can't open serial port"                 ;
$port->databits(8);
$port->baudrate(9600);
$port->parity("none");
$port->stopbits(1);
$port->handshake("none");
$port->write_settings || die "no settings";
 
# Read from serial device
my $buffer = "";
while(1) {
    while ($string_in = $port->input) {
        $buffer .= $string_in;
        while ($buffer =~ m/\n/) {
                ($buffer, $rest) = split('\n',$string_in);
                process_temp($buffer);
                $buffer = $rest;
        }
     }
     sleep(1);
}
exit;
 
sub process_temp() {
  my ($output) = @_;
  my ($romid, $temp) = split(' ', $output, 2);
 
  my $location = $rommap{$romid};
  if ($location eq "") {
     $location = $romid;
  }
  # We update the temperature when a new value is received.
  # This file will be polled and logged by another
  $temp =~ s/[\r\n]//g;
  $file = "/var/run/1wire/$location";
  open(FH,">$file") || die "cannot open $file for write";
  print FH "$temp\n";
  close(FH);
}

We run this PERL code in a cron job that poll every 5 mins and transmits the value to the RRD server.

*/5 * * * * /usr/local/bin/xmit_temp.pl >/dev/null
#!/usr/bin/perl
#
# rrd_hddtemp.pl
 
use IO::Socket;
use File::Basename;
 
# Remote RRD server and port
my $host = "elmo";
my $port = 13900;
 
my $socket = IO::Socket::INET->new(PeerAddr=> $host,
                                PeerPort=> $port,
                                Proto=> 'tcp',
                                Type=> SOCK_STREAM)
                                or die "Can't talk to $host at $port";
@files = </var/run/1wire/*>;
foreach $file (@files) {
   &ProcessHDD($socket, $file);
}
 
close $socket;
 
sub ProcessHDD
{
    my($socket,$file) = @_;
    open(FH,$file);
    $temp = <FH>;
    close FH;
 
    $loc = basename($file);
    $temp =~ s/[\n ]//g;
#    print "$loc : $temp degrees C\n";
 
    print $socket "update $loc.rrd -t temp N:$temp\n" ;
    $answer = <$socket>;
    print $answer;
}