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);
}