temp. logger WL500gp

New hardware project from me. This time I will show you, how make a temperature logger from your WL500g Premium router. On the mainboard of this router you can find two UART ports and I was wondering how to use this advantage of this very nice box. The first idea was to design a device that can be attached to router and the device will be sending current temerature of your room/inside into router. Internal software will receive this temperature save and append to a file on harddrive. Router will be also running a httpd server with PHP support. And when you type IP address of your router, it will show you nice page with graph of stored temeratures. Nice plan, isn't it ? :)





The device

The serial device we will attach to router consists of PIC microcontroller, a few SMT components (resistors, capacitor) and a DS1820 - digital temperature sensor. On the left side is Pickit 2 header for programming, on the bottom header for connecting DS1820, all of the components are soldered on top of the board except of the UART 2x4 pinheader !
Schematic
 Download EAGLE Schematic

PCB diagram (negative)
 Download EAGLE Board

PCB etching diagram @ 600dpi (50%)
 Download @ 600 dpi





The software

Now you can connect the device with your router. If everything works, after typing cat /dev/tts/1 you should see incoming data (note that you can use minicom utility to test serial communication) :
Thermolog output
DS: Scratchpad [32004b46ffff091094]
DS: Temperature [0193]= 25.1875 °C
DS: Scratchpad [32004b46ffff091094]
DS: Temperature [0193]= 25.1875 °C
DS: Scratchpad [33004b46ffff081013]
DS: Temperature [019c]= 25.7500 °C
DS: Scratchpad [33004b46ffff081013]
DS: Temperature [019c]= 25.7500 °C
DS: Scratchpad [33004b46ffff081013]
DS: Temperature [019c]= 25.7500 °C
DS: Scratchpad [33004b46ffff081013]
DS: Temperature [019c]= 25.7500 °C
DS: Scratchpad [33004b46ffff081013]
DS: Temperature [019c]= 25.7500 °C
DS: Scratchpad [33004b46ffff07100b]
DS: Temperature [019d]= 25.8125 °C
 

Ok, now we will parse this text to obtain temperature. We will not store the number in RAW form instead of a decimal number form. Look at this string DS: Temperature [019c]= 25.7500 °C - the number inside the [] parenthesis 019C is the raw temperature, skip the last digit (c) and you will get 019h = 25 °C, the last digit represent 1/16 of degree, that means, resolution od DS1820 thermometer is 1/16 of digit. Probably the best way to parse this line to get raw temperature can be achieved using regular expressions, but I absolutelly dont understand them :) Luckily, you can combine commandline functions like cut, head, grep, tr, but it doesn't look very nice....
Thermolog parser
#!/bin/sh

((date +%s,60, | tr -d "\n") && (cat /dev/tts/1 | 
head -n 3 | grep ]= | cut -d[ -f2 | cut -d] -f1)) 
>> /tmp/harddisk/thermolog
 

This command will get 3 lines (head -n 3) from UART (cat /dev/tts/1), parses out the raw temperature code that is between [ and ]= (grep ]= | cut -d[ -f2 | cut -d] -f1) and appends to a file on harddrive (>> /tmp/harddisk/thermolog). On the begin of each line, it will put current date time timestamp and number 60 - to let the PHP code know, when was the temperature received, and for what time period (60 seconds), the number 60 is not important for now... Save this piece of code into /opt/etc/cron.1min, make it executable (chmod +x), and make an entry in cron table to run it every minute. Inside /tmp/harddisk/thermolog file you will find something like:
thermolog file
1197201422,60,01ce
1197201481,60,01c3
1197201541,60,01be
1197201602,60,01bc
1197201661,60,01bf
1197201721,60,01c0
1197201782,60,01c3
1197201841,60,01c2
1197201901,60,01c2
1197201961,60,01c1
1197202021,60,01c3
1197202082,60,01c2
1197202141,60,01c1
1197202201,60,01bf
1197202262,60,01c0
1197202441,60,01c0
1197202501,60,01bf
1197202561,60,01bf
1197202621,60,01bf
1197202681,60,01c0
1197202741,60,01c0
1197202801,60,01c0
1197202861,60,01bf
1197202921,60,01bf
 





The PHP script

The task of PHP script to open logfile, parse it and draw a graph from it, you can find many free PHP graph-drawing source codes, and I choosed JP Graph library. Later I realized it is too CPU-eating, to draw chart from small file takes about 6 seconds and from 800 kB about 15 seconds...

delay.h
<?php
include ("plugins/jpgraph/jpgraph.php");
include ("plugins/jpgraph/jpgraph_line.php");

$firsttime = 0;

// The callback that converts timestamp to minutes and seconds
function TimeCallback($aVal) {
    global $firsttime;
    return Date("H:i",$aVal+$firsttime);
}

$f = fopen("/tmp/harddisk/thermolog", "r");
$line = 0;
$min = 100;
$max = 0;
$time = time();
$skipcount = 0;

while (!feof($f))
{
  $buffer = fgets($f, 32);
  $buffer = str_replace(array("\r", "\n"), "", $buffer);
  list($tm, $dur, $hextemp) = explode(",", $buffer);

  if (strlen($hextemp)!=4)
    continue;
  if ($tm <= 0)
    continue;
  if ($time-$tm > 60*60*48)
    continue;

  $line++;
  if ($skipcount > 1)
  {
    if ( $line % $skipcount != 0 )
      continue;
  }

  if ($firsttime == 0)
  {
    $firsttime = $tm*1;
    $reccnt = ($time-$firsttime)/60; // count of lines in file
    $skipcount = floor($reccnt/200);
  }

  $temp = hexdec($hextemp)/16;
  if ($temp > $max)
    $max = $temp;
  if ($temp < $min)
    $min = $temp;

  $datay[] = $temp;
  $datax[] = $tm-$firsttime;
}

$min = round($min-9, -1);
$max = round($max+5, -1);

$graph = new Graph(324,250);                                                    
$graph->SetMarginColor("white");
$graph->SetMargin(50,30,30,60);  
$graph->title->Set("Inner temperature = ".$temp." °C");
$graph->SetAlphaBlending();
$graph->SetScale("intlin",$min,$max,$now,$datax[$n-1]);
$graph->xaxis->SetLabelFormatCallback("TimeCallback");
$graph->xaxis->SetLabelAngle(90);
$p1 = new LinePlot($datay,$datax);
$p1->SetColor("blue");
$p1->SetFillColor("blue@0.4");
$graph->Add($p1);
$graph->Stroke();
?>
 





The result :)

Thermograph output
 ...after opening the box

Firmware

The PIC talks to DS1820 device, takes out the scratchpad RAM, generates temperature from it and sends it over software emulated (bit-banged) serial link.

main.c
#include <stdio.h>
#include <htc.h>

__CONFIG( BORDIS & UNPROTECT & MCLRDIS & WDTDIS & INTIO );

//#include "config.h"
#include "delay.h"
#include "ds1820.h"

void HW_Init(void)
{

  RP0 = 0;
  GPIO = 0;
  CMCON0 = 0;
  RP0 = 1;

  OPTION = 0b10000000;
  PIE1 = 0;
  ANSEL = 0b00000000;    
  INTCON = 0;
  CCP1CON = 0;
  di();
  printf("DS: Thermologger ver. 1.2 by gabonator@inmail.sk" RET);
}

void printhexdigit(char a)
{
  putch("0123456789abcdef"[a]);
}
void printhex(char a)
{
  printhexdigit(a>>4);
  printhexdigit(a&0xf);
}
void printtemp(int nTemp)
{
  // print decimals
  int nDecimal = nTemp >> _tempMulBits;
  int nFloat = nTemp & (_tempMul-1);
  nFloat *= 10000 / _tempMul;
  
  printf("%d.", nDecimal);

  if ( nFloat < 1000 )
    putch('0');

  printf("%d", nFloat);
}

void HW_Do(void)
{
  int nTemp = GetDS1820();
  char i;

  if ( DS_ERROR( nTemp ) )
  {
    switch (nTemp)
    {
      case DS_ERR_INIT1:
        printf("DS: Error init 1 !" RET);
        break;
      case DS_ERR_INIT2:
        printf("DS: Error init 2 !" RET);
        break;
      case DS_ERR_COMP:
        printf("DS: Compatibility error" RET);
        break;
      default:
        printf("DS: Unknown error" RET);
    }
  } else 
  {
    printf("DS: Scratchpad [");
    for (i = 0; i<9; i++)
      printhex(buff[i]);
    printf("]" RET);
    printf("DS: Temperature [");
    printhex( nTemp >> 8 );
    printhex( nTemp & 0xff );
    printf("]= ");
    printtemp( nTemp );
    printf(" °C" RET);
  }
}

void main(void)
{
  HW_Init();
  while (1)  
    HW_Do();
}
 Download

DS1820.h
#if (!defined(DS_Pin)) || (!defined(DS_Dir))
#define DS_Pin GPIO2
#define DS_Dir TRISIO2
#endif

#define BOOL unsigned char
#define FALSE 0
#define TRUE 1

BOOL DS_Init( void );
void DS_Out( unsigned char );
void DS_Power( void );
unsigned char DS_In( void );
void DS_Hi( void );
void DS_Low( void );
void DS_Error(void);

#define _ASSERT(x)

#define DS_REG_TempLSB 0
#define DS_REG_TempMSB 1
#define DS_REG_UserTh 2
#define DS_REG_UserTl 3
#define DS_REG_CntRemain 6
#define DS_REG_CntPerDeg 7
#define DS_REG_CntPerCrc 8

// nefunguje error checking
#define DS_ERROR(a) ( a == 0x8000 || a == 0x8001 || a == 0x8002 )
#define DS_ERR_INIT1  0x8000
#define DS_ERR_INIT2  0x8001
#define DS_ERR_COMP    0x8002

// scratchpad ram
unsigned char buff[9];

int GetDS1820(void)
{
  unsigned char n;
  unsigned int temp;

   if (!DS_Init())
    return DS_ERR_INIT1;

  DS_Out(0xcc);  // skip ROM
  DS_Out(0x44);  // perform temperature conversion

  // ak vracia 0xAA skontrolovat typ obvodu, musi DS1820
  //DS_Power();

  // konverzia max.750ms
  DelayMs(250);  
  DelayMs(250);  
  DelayMs(250);  
  
  if (!DS_Init())
    return DS_ERR_INIT2;
  
  DS_Out(0xcc);
  DS_Out(0xbe);  // read result
  
  for (n = 0; n < 9; n++)
    buff[n] = DS_In();

  temp = buff[1];
  temp <<= 8;
  temp |= buff[0];

#define _tempMulBits 4
#define _tempMul 16

  temp <<= _tempMulBits-1;  //temp *= _tempMul/2;
  // teplota je v °C * _tempMul
  
  // extended measurement
  if ( buff[DS_REG_CntPerDeg] != 0x10 )
    return DS_ERR_COMP;

  temp -= _tempMul/4;  //0.25*_tempMul;
  temp += buff[DS_REG_CntPerDeg];
  temp -= buff[DS_REG_CntRemain];

  return temp;  
}

// 1wire 

BOOL DS_Init(void)
{
   DS_Hi();
   DS_Low();
   DelayUs(250);   
   DelayUs(250);
   DS_Hi();
   DelayUs(100);  // po nejakych 120 sa uvolni !

   if (DS_Pin)
    return FALSE;

   DelayUs(200);
   DelayUs(200);
   return TRUE;
}

unsigned char DS_In(void)
{
   unsigned char n, i_byte;

   for (n=0; n<8; n++)
   {
  
  DS_Dir = 0; DS_Pin = 0;
  DS_Dir = 1; 
#asm
      NOP
      NOP
      NOP
#endasm
      i_byte >>= 1;
      if ( DS_Pin )
        i_byte |= 0x80;  // least sig bit first

      DelayUs(60);
   }
   return i_byte;
}

void DS_Out(unsigned char d)
{
  unsigned char n;
  for(n=0; n<8; n++)
  {
    DS_Dir=0;
    DS_Pin=0;

    if (d & 1)
    {
      DS_Dir = 1;
      DelayUs(60);
    } else
    {
      DelayUs(60);
      DS_Dir = 1;
        }
        d >>= 1;
   }
}

void DS_Hi(void)
{
  DS_Dir = 1;
}

void DS_Low(void)
{
  DS_Dir = 0;
  DS_Pin = 0;
}

void DS_Power()  // bring DQ to strong +5VDC
{
  DS_Dir = 0;
  DS_Pin = 1;
  DelayMs(250);
  DS_Dir = 1;
}

 Download

serial.c
/*
 *  Serial port driver (uses bit-banging)
 *  for 16Cxx series parts.
 *
 *   IMPORTANT: Compile this file with FULL optimization
 *
 *  Copyright (C)1996 HI-TECH Software.
 *  Freely distributable.
 */
#include  <htc.h>
/*
 *  Tunable parameters
 */

/*  Xtal frequency */
#define  XTAL    4000000

/*  Baud rate  */
#define  BRATE    9600

/*  Transmit and Receive port bits */
#if ((!defined(TxData)) || (!defined(RxData)))
#define  TxData    GPIO5        /* Map TxData to pin */
#define  RxData    GPIO4        /* Map RxData to pin */
#define  TxDataDir  TRISIO5        /* Map TxData to pin */
#define  RxDataDir  TRISIO4        /* Map RxData to pin */
#endif
#define  INIT_PORT  TxDataDir = 0; RxDataDir = 1  /* set up I/O direction */


/*  Don't change anything else */
#define SCALER      10000000
#define ITIME      4*SCALER/XTAL  /* Instruction cycle time */
#if BRATE > 1200
  #define  DLY      3        /* cycles per null loop */
  #define  TX_OHEAD  13        /* overhead cycles per loop */
#else
  #define  DLY      9        /* cycles per null loop */
  #define TX_OHEAD  14
#endif
#define  RX_OHEAD    12        /* receiver overhead per loop */

#define  DELAY(ohead)  (((SCALER/BRATE)-(ohead*ITIME))/(DLY*ITIME))

void
putch(char c)
{
  unsigned char  bitno;
#if BRATE > 1200
  unsigned char  dly;
#else
  unsigned int  dly;
#endif

  INIT_PORT;
  TxData = 0;      /* start bit */
  bitno = 12;
  do {
    dly = DELAY(TX_OHEAD);  /* wait one bit time */
    do
      /* waiting in delay loop */ ;
    while(--dly);
    if(c & 1)
      TxData = 1;
    if(!(c & 1))
      TxData = 0;
    c = (c >> 1) | 0x80;
  } while(--bitno);
NOP();
}

char
getch(void)
{
  unsigned char  c, bitno;
#if BRATE > 1200
  unsigned char  dly;
#else
  unsigned int  dly;
#endif

  for(;;) {
    while(RxData)
      continue;    /* wait for start bit */
    dly = DELAY(3)/2;
    do
              /* waiting in delay loop */ ;
    while(--dly);
    if(RxData)
      continue;    /* twas just noise */
    bitno = 8;
    c = 0;
    do {
      dly = DELAY(RX_OHEAD);
      do
              /* waiting in delay loop */ ;
      while(--dly);
      c = (c >> 1) | (RxData << 7);
    } while(--bitno);
    return c;
  }
}

char
getche(void)
{
  char c;

  putch(c = getch());
  return c;
}
 Download

Photos

And finally, photos of prototype. This one is fully working, and is a little different from the schematics above. The only difference is, that PIC is in DIP package (with the same pinout) and RX, TX lines are swapped. Closeup pictures were taken with scanner, because the camera we have is.... dont know what is it suitable for, but surely not for taking phots...

This side
 

That side
 

Router
 

Constructed device