Data Logger, CCS PCM


// LOGGER_1.C  (CCS PCM, PIC16F84)
//
// This was one of a weekly project assigned in my undergraduate micro
// processor class.  This was written by Lawrence Taylor and Lisa
// Archer-Davies, Baltimore.  Both will be graduating in May, '99.
//
// The idea is a small unit that might be packed with strawberries,
// medicine or other temperature sensitive items during transit to 
// periodically log temperature and store the result in EEPROM.  We 
// used a 24C32 (4K X 8), but this could be modified to be a 65, 128 or 
// 24LC256.  
//
// On boot, the processor reads the LOG/DUMP input on RA.2.  If LOG
// the processor reads the NORM/INIT input on RA.3.  If INIT, the 
// next 24C32 memory address is intitialized to 0x0000 and
// this is saved to the 16F84's EEPROM and the processors goes into a 
// sleep mode.
//
// If LOG and NORM, the unit periodically measures temperature, fetches
// the address of the next 24C32 location from the 16F84 EEPROM, writes
// the temperature to this address in the 24C32, increments the address
// pointer and saves to the 16F84 EEPROM.  That is, the memory address 
// pointer is implemented using nonvolatile memory.  Thus, if power is
// lost, any data which has been logged is not overwritten.  Rather, 
// logging picks up where it left off.
//
// If DUMP, the logged data is displayed on a serial LCD.
//
// Timing between samples is performed using loop timing.
//
// An LED on RA.0 provides feedback to the user when the processor
// boots.  DUMP - 1 flash, INIT - 2 flashes, LOG NORM - 3 flashes.
//
// In this routine, a temperature measurement is not actually performed.
// Rather, get_temp() is a stub which returns various values.  This
// can be replaced with a measurment using the DS1621, DS1820 or similar
// or with any other measurement.
//
// copyright, Peter H. Anderson, Baltimore, MD, Apr, '99

#case

#include <16f84.h>
#include <defs_f84.h>

// delay routines
void delay_10us(int t);
void delay_ms(long t);

// i2c routines
byte i2c_in_byte(void);
void i2c_out_byte(byte o_byte);
void i2c_nack(void);
void i2c_ack(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_high_sda(void);
void i2c_low_sda(void);
void i2c_high_scl(void);
void i2c_low_scl(void);

// lcd routines
void lcd_init(void);
void out_RAM_str(int *s);
void lcd_hex_byte(int val);
void lcd_dec_byte(int val, int digits);
int num_to_char(int val);
void lcd_char(int ch);
void lcd_new_line(void);

void write_16f84_eeprom(int adr, int d);
int read_16f84_eeprom(int adr);
void write_32(long adr, int dat);
int read_32(long adr);
int get_temp(void);
long get_mem_ptr(void);
void save_mem(long current);
void flash(int blinks);

#define TxData 0	// RA.0 for serial LCD
#define SDA_PIN rb2	// RB.2
#define SCL_PIN rb1	// RB.1

#define SDA_DIR trisb2
#define SCL_DIR trisb1

#define MAX_ADR 0x2000
// for 24C32 - 0x1000, for 2465 - 0x2000, for 24128 - 0x4000
// for 24256 - 0x8000


main()
{
   long mem_adr;
   int y, n, t_f, location;

   trisa2 = 1;	// LOG/DUMP switch
   trisa3 = 1;	// NORM / INIT switch
   trisa1 = 0;	// LED

   lcd_init();

   if(!ra2)	//dump data
   {
      flash(1);	// flash LED one time
      location = get_mem_ptr(); // fetch ptr from 16F84 EEPROM
      for(n = 0; n < location; n++)
      {
	y = read_32(n);	// read byte from 2432 at adress n
	lcd_dec_byte(y, 2);	// and display
	lcd_char(' ');
	if ((((n + 1) % 4) == 0) && (n != 15))
        {
	   lcd_new_line();
	}
     }
#asm
   SLEEP
#endasm
}
   else	if(!ra3)	//initialize memory pointer
   {
      flash(2); // flash LED two times
      write_16f84_eeprom(0x00,0x00);
      write_16f84_eeprom(0x01,0x00);
#asm
   SLEEP
#endasm
   }

   else	// log the data
   {
      flash(3); // give some feedback
      do
      {
	 mem_adr = get_mem_ptr(); // get next available address
 	 t_f = get_temp(); // make a temperature measurment
  	 write_32(mem_adr, t_f); // save it to the 2432 EEPROM
	 ++mem_adr;
	 save_mem(mem_adr); // save next avaiable location

	 delay_ms(30000); // 30 seconds between samples
      }while(mem_adr < MAX_ADR);  // EEPROM full
#asm
   SLEEP
#endasm
   }
}


long get_mem_ptr(void) // fetch memory pointer from 16F84 EEPROM
{
   int hi, lo;
   hi = read_16f84_eeprom(0x00);
   lo = read_16f84_eeprom(0x01);
   return(hi << 8)|(lo);
}

void save_mem(long current)  // save memory pointer to 16F84
{
   write_16f84_eeprom(0x00,current >> 8);	// high byte
   write_16f84_eeprom(0x01,(current & 0xff));	// low byte
}

void flash(int blinks)	// flash LED blink times
{
   int n;
   for(n = 0; n < blinks; n++)
   {
      ra1 = 1;
      delay_ms(300);
      ra1 = 0;
      delay_ms(300);
   }
}

void write_32(long adr, int dat)	// write dat to adr in 24C32
{
   i2c_start();
   i2c_out_byte(0xa0);
   i2c_nack();
   i2c_out_byte((adr >>8)& 0xff);
   i2c_nack();
   i2c_out_byte(adr & 0xff);
   i2c_nack();
   i2c_out_byte(dat);
   i2c_nack();
   i2c_stop();
   delay_ms(25);	// wait for eeprom to program
}

int read_32(long adr)		// fetch data at adr in 24C32
{
   int y;

   i2c_start();
   i2c_out_byte(0xa0);
   i2c_nack();
   i2c_out_byte((adr >>8)& 0xff);
   i2c_nack();
   i2c_out_byte(adr & 0xff);
   i2c_nack();
   i2c_start();
   i2c_out_byte(0xA1);
   i2c_nack();
   y = i2c_in_byte();
   //i2c_nack();
   i2c_stop();
   return (y);
}

int read_16f84_eeprom(int adr)
{
   EEADR=adr;
   rd=1;	// set the read bit
   return(EEDATA);
}

void write_16f84_eeprom(int adr, int d)
{
   EEADR = adr;
   EEDATA = d;

   wren = 1;		// write enable
   EECON2 = 0x55;	// protection sequence
   EECON2 = 0xaa;

   wr = 1;		// begin programming sequnce
   delay_ms(20);
   wren = 0;		// disable write enable
}

int get_temp(void)	// this is simply a stub to return the number
{			// of degrees F
  static int val = 68;

  ++val;
  if(val>82)
  {
    val = 68;
  }
  return(val);
}

// delay routines
void delay_10us(int t)
{
#asm
      BCF STATUS, RP0
DELAY_10US_1:
      CLRWDT
      NOP
      NOP
      NOP
      NOP
      NOP
      NOP
      DECFSZ t, F
      GOTO DELAY_10US_1
#endasm
}

void delay_ms(long t)	// delays t millisecs
{
   do
   {
      delay_10us(100);
   } while(--t);
}

// i2c routines
byte i2c_in_byte(void)
{
   byte i_byte, n;
   i2c_high_sda();
   for (n=0; n<8; n++)
   {
      i2c_high_scl();
      if (SDA_PIN)
      {
	 i_byte = (i_byte << 1) | 0x01; // msbit first
      }
      else
      {
	 i_byte = i_byte << 1;
      }
      i2c_low_scl();
   }
   return(i_byte);
}

void i2c_out_byte(byte o_byte)
{
   byte n;
   for(n=0; n<8; n++)
   {
      if(o_byte&0x80)
      {
	 i2c_high_sda();
      }
      else
      {
	 i2c_low_sda();
      }
      i2c_high_scl();
      i2c_low_scl();
      o_byte = o_byte << 1;
   }
   i2c_high_sda();
}

void i2c_nack(void)
{
   i2c_high_sda();	// data at one
   i2c_high_scl();	// clock pulse
   i2c_low_scl();
}

void i2c_ack(void)
{
   i2c_low_sda();	// bring data low and clock
   i2c_high_scl();
   i2c_low_scl();
   i2c_high_sda();
}


void i2c_start(void)
{
   i2c_low_scl();
   i2c_high_sda();
   i2c_high_scl();	// bring SDA low while SCL is high
   i2c_low_sda();
   i2c_low_scl();
}

void i2c_stop(void)
{
   i2c_low_scl();
   i2c_low_sda();
   i2c_high_scl();
   i2c_high_sda();  // bring SDA high while SCL is high
   // idle is SDA high and SCL high
}

void i2c_high_sda(void)
{
   // bring SDA to high impedance
   SDA_DIR = 1;
   delay_10us(5);
}

void i2c_low_sda(void)
{
   SDA_PIN = 0;
   SDA_DIR = 0;  // output a hard logic zero
   delay_10us(5);
}

void i2c_high_scl(void)
{
   SCL_DIR = 1;   // high impedance
   delay_10us(5);
}

void i2c_low_scl(void)
{
   SCL_PIN = 0;
   SCL_DIR = 0;
   delay_10us(5);
}

// lcd routines
int num_to_char(int val)	// converts val to hex character
{
   int ch;
   if (val < 10)
   {
       ch=val+'0';
   }
   else
   {
     val=val-10;
     ch=val + 'A';
   }
   return(ch);
}

void lcd_char(int ch)	// serial output to PIC-n-LCD, 9600 baud
{
   int n, dly;
			// start bit + 8 data bits

#asm
   BCF STATUS, RP0
   MOVLW 9
   MOVWF n
   BCF STATUS, C

LCD_CHAR_1:

   BTFSS STATUS, C
   BSF PORTA, TxData
   BTFSC STATUS, C
   BCF PORTA, TxData
   MOVLW 32
   MOVWF dly

LCD_CHAR_2:
   DECFSZ dly, F
   GOTO LCD_CHAR_2
   RRF ch, F
   DECFSZ n, F
   GOTO LCD_CHAR_1

   BCF PORTA, TxData
   CLRWDT
   MOVLW 96
   MOVWF dly

LCD_CHAR_3:
   DECFSZ dly, F
   GOTO LCD_CHAR_3
   CLRWDT
#endasm
}

void lcd_init(void)	// sets TxData in idle state and resets PIC-n-LCD
{
#asm
   BCF STATUS, RP0
   BCF PORTA, TxData
   BSF STATUS, RP0
   BCF TRISA, TxData
   BCF STATUS, RP0
#endasm
   lcd_char(0x0c);
   delay_ms(250);
}

void lcd_new_line(void)	// outputs 0x0d, 0x0a
{
   lcd_char(0x0d);
   delay_ms(10);	// give the PIC-n-LCD time to perform the
   lcd_char(0x0a);	// new line function
   delay_ms(10);
}


void out_RAM_str(int s)
{
   while(*s)
   {
      lcd_char(*s);
      ++s;
   }
}

void lcd_hex_byte(int val) // displays val in hex format
{
   int ch;
   ch = num_to_char((val>>4) & 0x0f);
   lcd_char(ch);
   ch = num_to_char(val&0x0f);
   lcd_char(ch);
}

void lcd_dec_byte(int val, int digits)
// displays byte in decimal as either 1, 2 or 3 digits
{
   int d;
   int ch;
   if (digits == 3)
   {
      d=val/100;
      ch=num_to_char(d);
      lcd_char(ch);
   }
   if (digits >1)	// take the two lowest digits
   {
      val=val%100;
      d=val/10;
      ch=num_to_char(d);
      lcd_char(ch);
   }
   if (digits == 1)	// take the least significant digit
   {
      val = val%100;
   }

   d=val % 10;
   ch=num_to_char(d);
   lcd_char(ch);
}