4-Channel Pt100 Thermometer

Wichit Sirichote, wichit.sirichote@gmail.com

Build a circuit that displays four channels 0-100C using Pt100 as the temperature sensor.


The RTD or the Pt100 is one of the high accuracy temperature sensor for laboratory. Using the high resolution delta-sigma converter, enables designer to use a simple voltage divider circuit for measuring the resistance of the RTD without the need of any DC amplifications. This instrument shows how to use the LTC2420, 20-bit delta-sigma converter and the LM385 reference voltage for measuring four Pt100 sensors and displays on the text LCD.

Figure 1: The prototype board with Pt100 temperature sensor.

The real instrument is shown in Figure 2. The circuit is simple voltage divider. VREF and R1 are fixed values. R2 is the Pt100 sensor. Its resistance is changed with temperature. We can measure the sensor's resistance easily by detecting the voltage dropped across R2. The DC signal is equal to R2*(VREF/(R1+R2)). This signal can tie to the delta-sigma ADC directly.

Figure 2: The real instrument is simple voltage divider with reference voltage source.

The complete schematic is shown in Figure 3. The MCU is 40-pin DIP package Microchip PIC18F4620 running with internal oscillator. The low power 32768Hz oscillator is for 1s time base. PORTB, PB0-PB7 are for LCD 4-bit interface. The display is 16x2 text LCD. Reference voltage, +1.2V is produced by D2, LM385. This reference voltage is tied to U2 , LTC2420 and U1A, LM358. U3, CD4051 is 8-channel voltage multiplexer. This project uses only 4-channel, X0, X1, X2 and X3. The signal from the sensor for each channel is measured by the dropped voltage across each Pt100 sensor.

.
Figure 3: Hardware schematic (click to enlarge).

Figure 4 shows the board built by PIC project board with add on the buffer circuit, LM358.
Figure 4: The board is modified from PIC project board.

The sample sensor is a cheap two wires Pt100 sensor, WZP.

Figure 5: Pt100 sensor for four channels.

The boot message is shown in Figure 6.

Figure 6: Boot message.

The firmware provides the mode for adjusting value for each channel, +/-5C with 0.1C step. When power up the instrument, keep pressing left key will enter the Calibrate Mode. We can select channel to be adjusted by pressing the left key. The center key is for increasing +0.1C and the right key is for decreasing -0.1C. The adjusting value will be saved to internal EEPROM. Recycle the power will exit this mode and will return to normal operation. Note that the firmware for converting the Pt100's resistance to temperature is prepared for precision sensor. Thus for high accuracy Pt100, we can set all adjusting values to 0.0C.

Figure 7: Calibrate mode display.

 

Figure 8: Four sensors are immersed in the water bath.

The listings of source code is shown in Figure 9.

/*
 Pt100 Project
 4-channel Pt100 reading instrument
 Copyright (C) 2011 Wichit Sirichote
 
 4 April 2010 Fuse settings
 C800 161F 8F00 0081 
 15 July 2011 Pt100 project
 equation for 1.2398V reference voltage and 1k (985Ohms) + Pt100
     
   Y = 2592.6 * X - 297
   R^2 = 0.9998
   
   Y = A * X + B
 6 August 2011 add button for calibrating the sensor +/-10.0C with 0.1C step
   the conversion is for Pt100 with all correction values are 00.
   
   
   */
#define A 2614.6
   #define B -297
 
/*
   DS1302 RAM space total 31 bytes
   
   READ WRIRE
   0xC1 0xC0
   0xC3 0xC2
   0xC5 0xC4
   0xC7 0xC6
   0xC9 0xC8
   0xCB 0xCA
   
   
   ..........
   0xFD 0xFC
 
*/
void show_clock();
   void read_interval();
 
#define key1 PORTE.F2
   #define key2 PORTE.F1
   #define key3 PORTE.F0
#define ADC_CS1 PORTD.F5 // output bit
   #define ADC_SDO PORTD.F6 // input bit
   #define ADC_SCK PORTD.F7 // output bit
 
#define RED LATD.F4
   #define GREEN LATD.F3
   #define F0 LATB.F5
 
#define file_number 0 // address in EEPROM
   //#define interval 1 // interval setting
   #define name 2 // station name A-Z put in front of file name, e.g. BDATA001.TXT
// Gain = VREF/2^24
   // Gain = 1.235V/16777216
   //#define gain 0.000000073897838592529296875
// gain for LTC2420
   //#define gain 1.182556152E-6
#define gain 1.172447205E-6
#define gain_PIC 3.03157*3.22265625E-3
#define offset 100E-6 // DC offset of the LTC2400 approx. 200uV
int i,j,tmp;
   long sample;
char sec, trigger;
//char data[512];
   //char file[32];
   int ADC_buffer[8];
   char file_name[12] = "xDATAxxxCSV"; // say ADATA000.TXT
 
char num;
   char buffer[128];
   char buffer2[32];
   //short set_time[32];
   unsigned long x1,x2,x3,x4,x5;
   unsigned long y1,y2,y3,y4,y5;
   unsigned long z1,z2,z3,z4,z5;
   unsigned long u1,u2,u3,u4,u5;
unsigned long w1,w2,w3,w4,w5; // for LM35 five point moving average
   unsigned long v1,v2,v3,v4,v5; // for battery monitor five point moving average
unsigned long s1,s2,s3,s4,s5;
   unsigned long t1,t2,t3,t4,t5;
unsigned long r1,r2,r3,r4,r5;
 
int secs=0;
   int hour;
   int mins;
   int date,month,year;
short channel=0;
   short channel2=0;
 
int line; // line frequency for 50 or 60 Hz noise rejection
short fire=0;
   unsigned short character;
unsigned long size;
   unsigned long ADC0,ADC1,ADC2,ADC3; // 4-channel analog input
   unsigned long ADC4,ADC5; // for LM35 (ADC4) and battery voltage (ADC5)
   unsigned long ADC6,ADC7;
unsigned int VIN1,VIN2,VIN3,VIN4; // PIC's 10-bit ADC
unsigned int period;
   char set_interval;
   char oldname;
 
int TTL1,TTL2,TTL3;
long counter1=0;
int timer6=0;
   int timer7=0; // timeout for start recording
short calibrate=0; // calibrate flag
short offset1,offset2,offset3,offset4; // +127 -128
float temp_float;
// EEPROM space for saving the offset in
   #define save_offset1 5
   #define save_offset2 6
   #define save_offset3 7
   #define save_offset4 8
 
//******************* DS1302 driver **************************************
   #define RST PORTC.F2 // output bit
   #define IO PORTD.F4 // input/output bit
   #define SCLK PORTD.F3 // output bit
#define DIN0 PORTB.F0
 
//-------------------------------------------------------------------------
void fire_time ()
   {
   secs+=4;
   if (secs >= period) // test with 10 seconds
   {secs = 0;
   fire=1;
   }
 
}
/* read 32-bit data from LTC2400 */
unsigned long read_ADC1(void)
   {
   char k;
   long n;
 n= 0;
   ADC_CS1 = 0;
   for(k=0; k<32; k++)
   {
   n<<= 1;
   ADC_SCK = 1;
   n |= ADC_SDO;
   ADC_SCK = 0;
   }
   ADC_CS1 = 1;
 n&=0x1fffffff; // maskout sign bit
   if(n&0x10000000) return 0; // return 0 for Vin < 0
   n>>=8; // get 20-bit conversion result
   return n;
   }
/* 5-point moving average */
   // return data that scaled with reference voltage in uV unit
   unsigned long filter_ADC(void)
   {
   x5 = x4;
   x4 = x3;
   x3 = x2;
   x2 = x1;
   x1 = read_ADC1();
   return ((x1+x2+x3+x4+x5)/5);
   }
unsigned long filter_ADC1(void)
   {
   y5 = y4;
   y4 = y3;
   y3 = y2;
   y2 = y1;
   y1 = read_ADC1();
   return ((y1+y2+y3+y4+y5)/5);
   }
unsigned long filter_ADC2(void)
   {
   z5 = z4;
   z4 = z3;
   z3 = z2;
   z2 = z1;
   z1 = read_ADC1();
   return ((z1+z2+z3+z4+z5)/5);
   }
unsigned long filter_ADC3(void)
   {
   u5 = u4;
   u4 = u3;
   u3 = u2;
   u2 = u1;
   u1 = read_ADC1();
   return ((u1+u2+u3+u4+u5)/5);
   }
unsigned long filter_ADC4()
   {
   w5 = w4;
   w4 = w3;
   w3 = w2;
   w2= w1;
   w1 = read_ADC1();
   return (w1+w2+w3+w4+w5)/5;
   }
unsigned long filter_ADC5()
   {
   v5=v4;
   v4=v3;
   v3=v2;
   v2=v1;
   v1= read_ADC1();
   return (v1+v2+v3+v4+v5)/5;
   }
unsigned long filter_ADC6()
   {
   s5=s4;
   s4=s3;
   s3=s2;
   s2=s1;
   s1= read_ADC1();
   return (s1+s2+s3+s4+s5)/5;
}
unsigned long filter_ADC7()
   {
   t5=t4;
   t4=t3;
   t3=t2;
   t2=t1;
   t1= read_ADC1();
   return (t1+t2+t3+t4+t5)/5;
}
 
void read_dio()
   {
   if(channel2==0) VIN1=ADC_read(0);
   if(channel2==1) VIN2=ADC_read(1);
   if(channel2==2) VIN3=ADC_read(2);
   if(channel2==3) VIN4=ADC_read(3);
   channel2++;
   if(channel2>3) channel2=0;
 TTL1=0;
   TTL2=0;
   TTL3=0;
   if(PORTB&0x01) TTL1=1;
   if(PORTB&0x02) TTL2=1;
   if(PORTB&0x04) TTL3=1;
}
#define low_battery 4.0 // test with 6V seal lead acid
short alarm_batt=0;
void low_batt_detection()
   {
   static float d;
   d=ADC_read(3)*gain_PIC;
   if(d<low_battery) alarm_batt=1;
   else alarm_batt =0;
   // alarm_batt=1; // test
}
void alarm_low_batt()
   {
   if(alarm_batt)
   {
   RED=1;
   GREEN=0;
   Delay_ms(100);
   RED=0;
   Delay_ms(100);
   RED=1;
   GREEN=0;
   Delay_ms(100);
   RED=0;
   }
}
short gate=0;
   unsigned int counter2=0;
   unsigned int temp3;
void read_frequency()
   {
   gate^=1;
   if(gate) T0CON.F7=1; // feed clock to timer0
   else
   {
   T0CON.F7=0; // reset timer0
 counter2=TMR0L; // must read low byte beforehand
   temp3=TMR0H;
   temp3<<=8;
   counter2|=temp3;
 TMR0H=0;
   TMR0L=0;
   }
   }
   unsigned wind;
int average_wind()
   {
   r5=r4;
   r4=r3;
   r3=r2;
   r2=r1;
   r1=counter2;
   return (r1+r2+r3+r4+r5)/20; // divided with 5 and 4 to get cycle per second
   }
 
void wait_EOC()
   {
   ADC_SCK = 0;
   ADC_CS1 = 0; // pull CS low to monitor conversion complete
   while(ADC_SDO)
   continue; // wait EOC=1 until EOC=0
   ADC_CS1=1; // enter sleep
   }
 
short scan_flag=0;
void scan_all_channel()
   {
 scan_flag ^=1;
   if(scan_flag) Lcd_Custom_Out(2, 16," ");
   else Lcd_Custom_Out(2, 16,".");
 for(i=0; i<8; i++)
   {
 if(channel==0)
   {LATD &= 0xF8; // write 000 to MUX
   ADC7=filter_ADC(); // read battery voltage
 }
 if(channel==1)
   {LATD |= 0x01; // write 001 to MUX
   ADC0=filter_ADC1();
 }
   if(channel==2)
   {LATD &=0xF8; // write 002 to MUX
   LATD |= 0x02;
   ADC1=filter_ADC2();
 
 }
   if(channel==3)
   {
   LATD |= 0x03; // write 003 to MUX
   ADC2=filter_ADC3();
 }
   if(channel==4)
   {
   LATD &=0xF8;
   LATD |= 0x04; // write 004 to MUX
   ADC3=filter_ADC4();
 }
 if(channel==5)
   {
   LATD |= 0x05; // write 005 to MUX
   ADC4= filter_ADC5();
 }
 if(channel==6)
   {
   LATD &=0xF8;
   LATD |= 0x06; // write 005 to MUX
   ADC5= filter_ADC6();
 }
 if(channel==7)
   {
   LATD |= 0x07; // write 005 to MUX
   ADC6= filter_ADC7();
 }
 wait_EOC(); // wait end of conversion
 channel++;
   if(channel>7) channel=0;
   }
 }
short key=0;
   short press_key1=0;
   short press_key2=0;
   short press_key3=0;
 
 
// overflow every 4 seconds
void wait_tick()
   {
   //while(PIR1.TMR1IF==0)
   //asm CLRWDT ; // clear WDT
   //PIR1.TMR1IF=0;
   //TMR1H |= 0x80; // for one second tick
   //GREEN=1; // tick blink every four seconds
   //time();
 //RED=0;
 
 fire_time();
 //read_dio();
   //low_batt_detection();
   //alarm_low_batt();
 //read_frequency();
   //wind=average_wind();
 scan_all_channel();
 
}
 
void powerup_fill_data()
   {
 for(i=0; i<40; i++)
   {
   RED ^=1; // blink RED for analog scanning
   //delay_ms(400);
if(channel==0)
   {LATD &= 0xF8; // write 000 to MUX
   ADC7=filter_ADC(); // read battery voltage
 }
 if(channel==1)
   {LATD |= 0x01; // write 001 to MUX
   ADC0=filter_ADC1();
 }
   if(channel==2)
   {LATD &=0xF8; // write 002 to MUX
   LATD |= 0x02;
   ADC1=filter_ADC2();
 
 }
   if(channel==3)
   {
   LATD |= 0x03; // write 003 to MUX
   ADC2=filter_ADC3();
 }
   if(channel==4)
   {
   LATD &=0xF8;
   LATD |= 0x04; // write 004 to MUX
   ADC3=filter_ADC4();
 }
 if(channel==5)
   {
   LATD |= 0x05; // write 005 to MUX
   ADC4= filter_ADC5();
 }
 if(channel==6)
   {
   LATD &=0xF8;
   LATD |= 0x06; // write 005 to MUX
   ADC5= filter_ADC6();
 }
 if(channel==7)
   {
   LATD |= 0x07; // write 005 to MUX
   ADC6= filter_ADC7();
 }
 wait_EOC(); // wait end of conversion
   channel++;
   if(channel>7) channel=0;
 
 }
 
 }
 
 
void interrupt()
   {
 if(INTCON.INT0IF)
   {
   counter1++;
   INTCON.INT0IF=0; // clear INT0 flag
   // RED=1;
   // Delay_ms(50);
   // RED=0;
   }
   if(PIR1.TMR1IF)
   {
   PIR1.TMR1IF=0; // must be cleared by software
   // time(); // update clock every 4-second
 
 }
}
 
char *text = "4-Channel Pt100";
void init_lcd()
   {
   Lcd_Custom_Config(&PORTB,7,6,5,4,&PORTB,2,1,3); // Initialize LCD on PORTB
   Lcd_Custom_Cmd(Lcd_CURSOR_OFF); // Turn off cursor
   Lcd_Custom_Out(1, 1, text);
   Lcd_Custom_Out(2, 1, " Thermometer");
   }
void show_temperature()
   {
   static float d,d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,fracdate;
   //if(fire)
   {
   fire=0;
   //Mmc_fat_Append();
   d=ADC0*gain; //7.331371E-8; // convert to Volt VREF=1.230V
   d1=ADC1*gain; //7.331371E-8;
   d2=ADC2*gain; //7.331371E-8;
   d3=ADC3*gain; //7.331371E-8;
   d4=ADC4*gain; //7.331371E-6; // for LM35 internal sensor convert to degree C
   d5=ADC5*gain; //7.331371E-5; // monitor battery voltage divided by 1000
   d6=ADC6*gain; //7.331371E-5;
   d7=ADC7*gain; //7.331371E-5;
 d-=offset;
   d1-=offset;
   d2-=offset;
   d3-=offset;
   d4-=offset;
   d5-=offset;
   d6-=offset;
   d7-=offset;
   
   
   d= d*A + B;
   d1= d1*A + B;
   d2= d2*A + B;
   d3= d3*A + B;
   
   if (d>100) d=99.9;
   if (d1>100) d1=99.9;
   if (d2>100) d2=99.9;
   if (d3>100) d3=99.9;
   
   temp_float=1;
   temp_float*=offset1;
   d+=temp_float/10;
   
   temp_float=1;
   temp_float*=offset2;
   d1+=temp_float/10;
   
   temp_float=1;
   temp_float*=offset3;
   d2+=temp_float/10;
   
   temp_float=1;
   temp_float*=offset4;
   d3+=temp_float/10;
 
 //sprintf(buffer,"\r\n%ld,%d/%d/%d,%02d:%02d,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f",sample++,date,month,year,hour,mins,fracdate,d,d1,d2,d3,d4,d5,d6,d7);
   sprintf(buffer,"1=%.1fC",d);
   Lcd_Custom_Out(1, 1,buffer );
 sprintf(buffer,"3=%.1fC",d2);
   Lcd_Custom_Out(1,9,buffer );
 sprintf(buffer,"2=%.1fC",d1);
   Lcd_Custom_Out(2,1,buffer );
 sprintf(buffer,"4=%.1fC",d3);
   Lcd_Custom_Out(2, 9,buffer );
 
 }
   }
//short key=0;
   //short press_key1=0;
   //short press_key2=0;
   //short press_key3=0;
void read_key()
   {
   key=0;
   if(key1==0 && press_key1==0)
   {
   press_key1=1;
   key=1;
   }
 if(key2==0 && press_key2==0)
   {
   press_key2=1;
   key=2;
   }
   if((PORTE&1)==0 && press_key3==0)
   {
   press_key3=1;
   key=3;
   }
 
}
void key_release()
   {
   if(key1) press_key1=0;
   if(key2) press_key2=0;
   if(PORTE&1)press_key3=0;
   //if(key1&&(PORTE&1))timer6=0;
   }
int input_channel=0;
void service_key1()
   {
 temp_float=1;
   if(++input_channel>4) input_channel=1;
 switch (input_channel)
   {
   case 1: temp_float*=Eeprom_Read(save_offset1); sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 2: temp_float*=Eeprom_Read(save_offset2); sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 3: temp_float*=Eeprom_Read(save_offset3); sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 4: temp_float*=Eeprom_Read(save_offset4); sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   }
   
   Lcd_Custom_Out(2, 1,buffer);
}
void service_key2()
   {
 temp_float=1;
   switch (input_channel)
   {
   case 1: offset1+=1;Eeprom_Write(save_offset1,offset1); temp_float*=offset1; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 2: offset2+=1;Eeprom_Write(save_offset2,offset2); temp_float*=offset2; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 3: offset3+=1;Eeprom_Write(save_offset3,offset3); temp_float*=offset3; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 4: offset4+=1;Eeprom_Write(save_offset4,offset4); temp_float*=offset4; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   }
   Lcd_Custom_Out(2, 1,buffer);
   
   }
void service_key3()
   {
   temp_float=1;
 switch (input_channel)
   {
   case 1: offset1-=1; Eeprom_Write(save_offset1,offset1); temp_float*=offset1; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 2: offset2-=1; Eeprom_Write(save_offset2,offset2); temp_float*=offset2; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 3: offset3-=1; Eeprom_Write(save_offset3,offset3); temp_float*=offset3; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   case 4: offset4-=1; Eeprom_Write(save_offset4,offset4); temp_float*=offset4; sprintf(buffer,"CH%d %1.1f ",input_channel,temp_float/10);break;
   }
   Lcd_Custom_Out(2, 1,buffer);
   }
void scan_key()
   {
   delay_ms(30);
   read_key();
   key_release();
 if(key==1) service_key1(); // -
   if(key==2) service_key2(); // menu
   if(key==3) service_key3(); // +
   }
void enter_calibration_mode()
   {
   if(key1==0)
   {
   calibrate=1;
   Lcd_Custom_Cmd(Lcd_Clear);
   Lcd_Custom_Out(1, 1, "Calibrate Mode");
   }
   }
void read_offset_from_eeprom()
   {
   offset1= Eeprom_Read(save_offset1);
   offset2= Eeprom_Read(save_offset2);
   offset3= Eeprom_Read(save_offset3);
   offset4= Eeprom_Read(save_offset4);
   }
 
void main() {
 OSCCON=0x40; // 8MHz
   //OSCCON|=0x80; // enter idle mode when execute sleep instruction
 PORTD = 0x00;
   TRISD = 0x40; // PORTD is output RD6 is input bit
   // PORTB is output
   ADCON1 = 0x0A; // Configure analog inputs and Vref
   TRISA = 0xFF; // PORTA is ADC input
 TRISE |= 0x07; // PORTE is digital input
   PORTE = 0xff; //enable internal pullup
 TRISB=0x00;
   PORTB = 0xFF;
 TRISC =0x93;
   PORTC = 0xFF;
 
 F0=1; // 50Hz rejection frequency
 T1CON = 0x1F; // overflow every 4 seconds
 TMR1H=0;
   TMR1L=0;
 T0CON = 0xAF; // frequency input pulse
   T0CON.F7=0; // stop timer0
   TMR0H=0;
   TMR0L=0;
 INTCON = 0xC0; // enable GLOBAL interrupt pheriferal interrupt enable
 INTCON2 |= 0x80; // enable PORTB pull up
   //WPUB=0xF7; // pull up bits on PORTB
   PIR1.TMR1IF = 0; // clear TMR1IF
 init_lcd();
 Spi_Init_Advanced(MASTER_OSC_DIV4, DATA_SAMPLE_MIDDLE, CLK_IDLE_LOW, LOW_2_HIGH);
 
 RED=0;
 GREEN=0; // begin recording
   // T1CON = 0x1F; // overflow every 4 seconds
 PIE1.TMR1IE=1; // enable timer1 interrupt
     
   enter_calibration_mode(); // if Key1 was pressed when power up, enter calibrate mode
   offset1=0; // test for offset1
   
   read_offset_from_eeprom(); // restore offset from eeprom
   
   while(calibrate)
   {
   scan_key();
   }
 powerup_fill_data(); // fill data to the filter array
   Lcd_Custom_Cmd(Lcd_Clear);
   
   while(1)
   {
   scan_all_channel();
   show_temperature();
   }
}
Figure 9: Listings of the source program.

Download Schematic, Firmware (hex file for programming the PIC18F4620 using PICkit2 v2.40), BOM


6 August 2010