Linduino  1.3.0
Linear Technology Arduino-Compatible Demonstration Board
LTC2492_Thermocouple_Meter.ino
Go to the documentation of this file.
1 /*
2 Complete Digital Thermocouple Meter Using the LTC2492
3 24 bit Delta Sigma ADC with onboard temperature sensor
4 
5 Mark Thoren
6 Linear Technonlgy Corporation
7 September 9, 2004
8 
9 *** Much of this description needs updating - 4 line display, improved
10 curve fitting, LTC2492 stuff**********
11 
12 
13 Cold Junction Compensated from -40 to 85C (industrial temperature range)
14 using second order curve-fit.
15 Type K thermocouple range -100C to 300C. Fourth order curve fit is accurate
16 to 1 degree C in this range.
17 
18 Output is to a 2 line by 16 character LCD display using the standard
19 Hitachi 44780 (or compatible) controller.
20 
21 Example Output:
22 __________________
23 |V 2.35 TC 58.2 | (thermocouple voltage in mv, calculated thermocouple temp.)
24 |PT 424.1 CJ 21.5 | (PTAT voltage, calculated local temp.)
25 ------------------
26 
27 A calibration feature allows the inital error in the internal
28 reference and PTAT circuits to be calibrated out. The user applies a precise
29 500mV to the thermocouple input and measuring the local temperature of the LTC2480.
30 Output is calculated reference voltage on first line, calculated PTAT tempco and
31 local temperature on second line. During calibration, UP / DOWN buttons adjust
32 the local temperature in 0.1C increments. Pulling the jumper exits calibration
33 and kicks you into standard running mode.
34 
35 Example Calibration Screen:
36 __________________
37 |VREF:1.2401 | (Calculated Vref based on 500mV input)
38 |P:1.415 CJ:26.8 | (PTAT coefficient (mv/K), local temperature.)
39 ------------------
40 
41 *** Code was written for clarity, not efficiency. There are lots of extra
42 variables and steps that could be eliminated.
43 
44 Copyright 2011(c) Analog Devices, Inc.
45 
46 All rights reserved.
47 
48 Redistribution and use in source and binary forms, with or without modification,
49 are permitted provided that the following conditions are met:
50  - Redistributions of source code must retain the above copyright
51  notice, this list of conditions and the following disclaimer.
52  - Redistributions in binary form must reproduce the above copyright
53  notice, this list of conditions and the following disclaimer in
54  the documentation and/or other materials provided with the
55  distribution.
56  - Neither the name of Analog Devices, Inc. nor the names of its
57  contributors may be used to endorse or promote products derived
58  from this software without specific prior written permission.
59  - The use of this software may or may not infringe the patent rights
60  of one or more patent holders. This license does not release you
61  from the requirement that you obtain separate licenses from these
62  patent holders to use this software.
63  - Use of the software either in source or binary form, must be run
64  on or directly connected to an Analog Devices Inc. component.
65 
66 THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
67 INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A
68 PARTICULAR PURPOSE ARE DISCLAIMED.
69 
70 IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
71 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, INTELLECTUAL PROPERTY
72 RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
73 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
74 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
75 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
76 
77 
78 */
79 
80 #include <Arduino.h>
81 #include <stdint.h>
82 #include "Linduino.h"
83 #include "LTC2492_original.h"
84 #define HARDCODED_CONSTANTS
85 #include <LiquidCrystal.h>
86 #include <EEPROM.h>
87 #include <SPI.h>
88 #include <LT_SPI.h>
89 #include <Wire.h>
90 #include <LT_I2C.h>
91 #include "LTC24XX_general.h"
92 
93 #define CAL_JUMPER 1
94 #define INC_BUTTON 2
95 #define DEC_BUTTON 3
96 #define CS 9 // REMAPPED - requires a cut trace
97 
98 // LCD constants - This is a great pinout, does not step on any
99 // "Digital" pins,
100 const int16_t RS = 3;
101 const int16_t E = 2;
102 const int16_t D4 = A0;
103 const int16_t D5 = A1;
104 const int16_t D6 = A2;
105 const int16_t D7 = A3;
106 
107 struct fourbytes // Define structure of four consecutive bytes
108  { // To allow byte access to a 32 bit int or float.
109  int8_t te0; //
110  int8_t te1; // The make32() function in this compiler will
111  int8_t te2; // also work, but a union of 4 bytes and a 32 bit int
112  int8_t te3; // is probably more portable.
113  };
114 
115 // Global Variables.
116  // 'x' is a dummy for holding integer conversion result. It appears the compiler
117  // will only cast a variable, not a function. So if x is an int32, (float) x works
118  // but if function() returns an int32, (float) function() does not work.
119  int32_t x;
120 // float tc_voltage; // Measured thermocouple voltage
121  float cj_voltage; // Calculated cold junct voltage based on type K thermocouple
122 
123  float poly_voltage; // For raising tc_voltage to higher powers
124  float ptat_voltage; // Voltage from the internal PTAT circuit
125  float cj_raw;
126  float tc_raw;
127  float cj_temp; // Calculated cold junction temperature based on PTAT voltage
128 
129  float vref = 1.25;
130 
131  // vin_cal and ptat_cal are done this way to allow storing to local EEPROM
132  // as four bytes.
133 
134 //
135 // LT_union_float_4bytes vin_cal;
136 
137  union // vin_cal.fp 32 bit floating point
138  { // vin_cal.by.te0 byte 0
139  float fp; // vin_cal.by.te1 byte 1
140  struct fourbytes by; // vin_cal.by.te2 byte 2
141  } vin_cal; // vin_cal.by.te3 byte 3
142 
143  union // ptat_cal.fp 32 bit floating point
144  { // ptat_cal.by.te0 byte 0
145  float fp; // ptat_cal.by.te1 byte 1
146  struct fourbytes by; // ptat_cal.by.te2 byte 2
147  } ptat_cal; // ptat_cal.by.te3 byte 3
148 
149 // initialize the library with the numbers of the interface pins
150 LiquidCrystal lcd(RS, E, D4, D5, D6, D7);
151 
152 
153 /*** setup() **************************************************************
154 Basic hardware initialization of controller and LCD, send Hello message to LCD
155 *******************************************************************************/
156 void setup()
157  {
158  // General initialization stuff.
159 // setup_adc_ports(NO_ANALOGS);
160 // setup_adc(ADC_OFF);
161 // setup_counters(RTCC_INTERNAL,RTCC_DIV_1);
162 // setup_timer_1(T1_DISABLED);
163 // setup_timer_2(T2_DISABLED,0,1);
164 
165 // This is the important part - configuring the SPI port
166 // setup_spi(SPI_MASTER|SPI_L_TO_H|SPI_CLK_DIV_16|SPI_SS_DISABLED); // fast SPI clock
167 // CKP = 0; // Set up clock edges - clock idles low, data changes on
168 // CKE = 1; // falling edges, valid on rising edges.
169 
170  lcd.begin(16, 4); // Initialize LCD
171  Serial.begin(115200); // Initialize the serial port to the PC
172 
173  // Display Message
174  lcd.print("LTC2492 Type K TC Meter");
175  Serial.println("LTC2492 Type K TC Meter");
176  delay(200);
177  quikeval_SPI_init(); // Configure the spi port for 4volts SCK
178 // quikeval_I2C_init(); // Configure the EEPROM I2C port for 100kHz
179  quikeval_SPI_connect(); // Connect SPI to main data port
180 
181  // lcd_init(); // Initialize LCD
182  // delay_ms(6);
183  // printf(lcd_putc, "Hello!"); // Obligatory hello message
184  // delay_ms(500); // for half a second
185  } // End of initialize()
186 
187 
188 /*** main() ********************************************************************
189 Main program initializes microcontroller registers, checks to see if calibration
190 mode is selected, reads calibration constants from nonvolatile memory.
191 Main loop reads ADC input and PTAT voltage, calculates temperatures and voltages,
192 then sends this information to the display.
193 *******************************************************************************/
194 void loop()
195  {
196 
197  float ch0_voltage, ch0_temp, ch1_voltage, ch1_temp;
198 // initialize();
199 // if(!digitalRead(CAL_JUMPER)) calibrate(); // See if calibration jumper is installed
200  // Read stored calibration values from first 8 EEPROM locations.
201 
202 // output_high(PIN_C1);
203 // read_calibration();
204 
205  while(1)
206  {
207  Serial.println("Reading ADC...");
210 
211 // x = read_LTC2492(CH0_1, VIN | R55 | SLOW); // Read PTAT voltage, program to read input 0-1
212  Serial.print("Got Code: "); Serial.println(x, BIN);
213  Serial.println("Calculating Temp");
214  // If we have room, check to see if there is a big step function, then reset filter.
215 // cj_raw = 0.95 * cj_raw + 0.05 * (float) x; // convert to float and apply digital filter
216  cj_raw = (float) x;
217  // 500 is the assumed calibration signal (see calibrate function)
218  ptat_voltage = LTC24XX_diff_code_to_voltage(x, vref);; // this is not necessary, but useful for debugging.
219  cj_temp = (ptat_voltage / 0.0000935) - 273.15; // Calculate local temperature in C
220 
221  // Compensate measured thermocouple voltage for cold junction temperature based on
222  // second order curve fit. Second order is adequate because it only needs to
223  // compensate from -40 to 85C (industrial temperature range for LTC24XX.)
224  cj_voltage = .0005265 + .0393 * cj_temp; // Constant and 1st order term
225  cj_voltage += .000018477 * cj_temp * cj_temp; // Squared term
226 
229  x = read_LTC2492(CH2_3, VIN | R55 | SLOW); // Read input 0-1 voltage, program to convert input 0-1
230  ch0_voltage = LTC24XX_diff_code_to_voltage(x, vref);
231  ch0_temp = type_K_V2C(ch0_voltage, cj_voltage);
232 
235  x = read_LTC2492(NO_CH, PTAT | R55); // Read input voltage, program to convert PTAT
236  ch1_voltage = LTC24XX_diff_code_to_voltage(x, vref);
237  ch1_temp = type_K_V2C(ch1_voltage, cj_voltage);
238 
239 
240  lcd.setCursor(1,1);
241  Serial.print("0T "); Serial.println(ch0_temp, 1);
242  lcd.setCursor(11,1);
243  Serial.print("1T "); Serial.println(ch1_temp, 1);
244 
245  lcd.setCursor(1,2);
246  Serial.print("0V "); Serial.println(ch0_voltage,3);
247  lcd.setCursor(11,2);
248  Serial.print("1V "); Serial.println(ch1_voltage,3);
249 
250 // Print calculated thermocouple temperature
251 // lcd.setCursor(8,1);
252 
253 // Print PTAT circuit voltage
254  lcd.setCursor(1,3);
255  Serial.print("CJ TEMP %01.1f "); Serial.print(cj_temp, 1);
256 // Print local temperature based on PTAT voltage
257  lcd.setCursor(1,4);
258  Serial.print("PT V "); Serial.print(ptat_voltage,2); Serial.print("CJ V "); Serial.println(cj_voltage, 2);
259  } // End of main loop
260  } // End of main()
261 
262 
263 
264 //////////////////////////////////////////////////////////////////////////////
265 // Calculate type K thermocouple temperature in degrees C from thermocouple //
266 // voltage in volts and cold junction temperature in degrees C. //
267 //////////////////////////////////////////////////////////////////////////////
268 float type_K_V2C(float tc_voltage, float cj_voltage)
269  {
270  float tc_temp; // Calculated thermocouple temperature
271  tc_voltage = tc_voltage + cj_voltage; // Adjust thermocouple voltage
272 // Calculate temperature based on 5th order curve fit.
273 // These values are 3 degrees accurate from -100C to +1370C.
274  tc_temp = 0.0; // a0 (constant)
275  poly_voltage = tc_voltage; // First power
276  tc_temp += poly_voltage * 25.57848; // a1
277  poly_voltage = poly_voltage * tc_voltage; // Squared
278  tc_temp += poly_voltage * -0.1165412; // a2
279  poly_voltage = poly_voltage * tc_voltage; // Cubed
280  tc_temp += poly_voltage * 0.002922928; // a3
281  poly_voltage = poly_voltage * tc_voltage; // Fourth power
282  tc_temp += poly_voltage * -0.00003270702; // a4
283  poly_voltage = poly_voltage * tc_voltage; // Fifth power
284  tc_temp += poly_voltage * 0.0000002670777; // a5
285  return tc_temp;
286  }
287 
288 
289 /*** read_LTC2480() ************************************************************
290 This is the funciton that actually does all the work of talking to the LTC2480.
291 the spi_read() function performs an 8 bit bidirectional transfer on the SPI bus.
292 Data changes state on falling clock edges and is valid on rising edges, as
293 determined by the setup_spi() line in the initialize() function.
294 
295 A good starting point when porting to other processors is to write your own
296 spi_write function. Note that each processor has its own way of configuring
297 the SPI port, and different compilers may or may not have built-in functions
298 for the SPI port. Also, since the state of the LTC2480's SDO line indicates
299 when a conversion is complete you need to be able to read the state of this line
300 through the processor's serial data input. Most processors will let you read
301 this pin as if it were a general purpose I/O line, but there may be some that
302 don't.
303 
304 When in doubt, you can always write a "bit bang" function for troubleshooting
305 purposes.
306 
307 The "fourbytes" structure allows byte access to the 32 bit return value:
308 
309 struct fourbytes // Define structure of four consecutive bytes
310  { // To allow byte access to a 32 bit int or float.
311  int8 te0; //
312  int8 te1; // The make32() function in this compiler will
313  int8 te2; // also work, but a union of 4 bytes and a 32 bit int
314  int8 te3; // is probably more portable.
315  };
316 
317 Also note that the lower 4 bits are the configuration word from the previous
318 conversion. The 4 LSBs are cleared so that
319 they don't affect any subsequent mathematical operations. While you can do a
320 right shift by 4, there is no point if you are going to convert to floating point
321 numbers - just adjust your scaling constants appropriately.
322 *******************************************************************************/
323 int32_t read_LTC2492(char channel, char config)
324  {
325  union // adc_code.bits32 all 32 bits
326  { // adc_code.by.te0 byte 0
327  int32_t bits32; // adc_code.by.te1 byte 1
328  struct fourbytes by; // adc_code.by.te2 byte 2
329  } adc_code; // adc_code.by.te3 byte 3
330 
331  output_low(CS); // Enable LTC2480 SPI interface
332  delay(150);
333  while(digitalRead(MISO)) {} // Wait for end of conversion. The longest
334  // you will ever wait is one whole conversion period
335 
336  adc_code.by.te3 = spi_read(channel); // Set to zero.
337  adc_code.by.te2 = spi_read(config); // Read first byte, send config byte
338  adc_code.by.te1 = spi_read(0); // Read 2nd byte, send speed bit
339  adc_code.by.te0 = spi_read(0); // Read 3rd byte. '0' argument is necessary
340  // to act as SPI master!! (compiler
341  // and processor specific.)
342  output_high(CS); // Disable LTC2480 SPI interface
343  adc_code.bits32 -= 536870912;
344  return adc_code.bits32;
345  } // End of read_LTC2480()
346 
347 
348 
349 
350 
351 /*** calibrate() ***************************************************************
352 Calibration routine. During calibration, apply 500mV to inputs and adjust local
353 temperature display to match a known good temperature reading. Pull jumper when
354 finished.
355 *******************************************************************************/
356 void calibrate(void)
357  {
358 // #ifdef HARDCODED_CONSTANTS
359 
360 
361 
362 // #else
363 
365  cj_temp = 300.15; // Initial calibration guess
366 // lcd_putc('\f');
367  lcd.setCursor(1,1);
368 // printf(lcd_putc, "vin_c %02X %02X %02X %02X ", vin_cal.by.te3, vin_cal.by.te2, vin_cal.by.te1, vin_cal.by.te0);
369  Serial.print("vin_c dummy");
370  lcd.setCursor(1,2);
371 // printf(lcd_putc, "ptat_c %02X %02X %02X %02X ", ptat_cal.by.te3, ptat_cal.by.te2, ptat_cal.by.te1, ptat_cal.by.te0);
372  Serial.print("ptat_c dummy");
373 
374  while(digitalRead(INC_BUTTON)); // Wait for INC button to be pressed.
375 
376  while(!digitalRead(CAL_JUMPER)) // Check state of calibration jumper
377  {
378 
379 // Read input voltage, program to read PTAT
380  x = read_LTC2492(NO_CH, PTAT | R55);
381  vin_cal.fp = (float) x; // Calculate Counts / 500mV
382 
383 // Read PTAT voltage, program to read input
384  x = read_LTC2492(CH0_1, VIN | R55 | SLOW);
385 
386 // Calculate Counts / Kelvin based on PTAT voltage and
387 // local temperature entered from pushbuttons
388  ptat_cal.fp = (float) x / cj_temp;
389 
390 // Display calculated reference voltage (nominally 1.22V). The 1048576 factor
391 // is results from the 500mV assumed calibration constant and the ADC full-scale
392 // output of 2^20.
393  lcd.setCursor(1,1);
394 // printf(lcd_putc, "VREF:%1.4f", ((0.8 * 1.25 * 268435456.0) / vin_cal.fp));
395 
396 // Display calculated PTAT slope (nominally 1.4mV/K) and local temp in Celcius.
397  lcd.setCursor(1,2);
398 // printf(lcd_putc,"P:%1.3f CJ:%1.1f",
399 // (ptat_cal.fp * 500 / vin_cal.fp), (cj_temp-273.15));
400  //lcd.setCursor(1,3);
401  delay(50);
402 
403 // Adjust local temperature. 50ms delay above functions as a crude
404 // De-bounce and auto-repeat.
405  if(!digitalRead(INC_BUTTON)) cj_temp += 0.1; // Increment local temperature
406  if(!digitalRead(DEC_BUTTON)) cj_temp -= 0.1; // Decrement local temperature
407  }
408 
409 // If you are here, jumper was pulled. Write constants to nonvolatile memory.
410 // The EEPROM.write function writes to the controller's onboard memory. This
411 // is compiler and processor specific.
412  EEPROM.write(0, ptat_cal.by.te0);
413  EEPROM.write(1, ptat_cal.by.te1);
414  EEPROM.write(2, ptat_cal.by.te2);
415  EEPROM.write(3, ptat_cal.by.te3);
416  EEPROM.write(4, vin_cal.by.te0);
417  EEPROM.write(5, vin_cal.by.te1);
418  EEPROM.write(6, vin_cal.by.te2);
419  EEPROM.write(7, vin_cal.by.te3);
420 // Return to main program loop.
421 // #endif
422  } // End of calibrate()
423 
425  {
426 #ifdef HARDCODED_CONSTANTS
427 
428  ptat_cal.by.te0 = 0x8E;
429  ptat_cal.by.te1 = 0x1C;
430  ptat_cal.by.te2 = 0xA9;
431  ptat_cal.by.te3 = 0xB0;
432  vin_cal.by.te0 = 0x9A;
433  vin_cal.by.te1 = 0x4C;
434  vin_cal.by.te2 = 0xCC;
435  vin_cal.by.te3 = 0x1B;
436 
437 #else
438 
439  ptat_cal.by.te0 = EEPROM.read(0);
440  ptat_cal.by.te1 = EEPROM.read(1);
441  ptat_cal.by.te2 = EEPROM.read(2);
442  ptat_cal.by.te3 = EEPROM.read(3);
443  vin_cal.by.te0 = EEPROM.read(4);
444  vin_cal.by.te1 = EEPROM.read(5);
445  vin_cal.by.te2 = EEPROM.read(6);
446  vin_cal.by.te3 = EEPROM.read(7);
447 
448 #endif
449 
450  }
#define VIN
LiquidCrystal lcd(RS, E, D4, D5, D6, D7)
static void calibrate(void)
const int16_t D4
#define output_high(pin)
Set "pin" high.
Definition: Linduino.h:75
#define LTC24XX_MULTI_CH_P1_N0
Header File for Linduino Libraries and Demo Code.
const int16_t D5
const int16_t RS
int8_t LTC24XX_EOC_timeout(uint8_t cs, uint16_t miso_timeout)
Checks for EOC with a specified timeout.
static void read_calibration(void)
struct fourbytes by
static uint8_t channel
LTC2305 Channel selection.
Definition: DC1444A.ino:127
static int32_t read_LTC2492(char channel, char config)
const int16_t D6
#define CH2_3
static void loop()
static void setup()
#define SLOW
#define INC_BUTTON
LTC24XX General Library: Functions and defines for all SINC4 Delta Sigma ADCs.
void LTC24XX_SPI_16bit_command_32bit_data(uint8_t cs, uint8_t adc_command_high, uint8_t adc_command_low, int32_t *adc_code)
Reads from LTC24XX ADC that accepts a 16 bit configuration and returns a 32 bit result.
#define DEC_BUTTON
const int16_t D7
#define LTC24XX_MULTI_CH_P3_N2
#define output_low(pin)
Set "pin" low.
Definition: Linduino.h:72
union @0 vin_cal
void quikeval_SPI_init(void)
Configure the SPI port for 4Mhz SCK.
Definition: LT_SPI.cpp:151
#define NO_CH
LT_SPI: Routines to communicate with ATmega328P&#39;s hardware SPI port.
#define LTC24XX_EZ_MULTI_PTAT
#define PTAT
LT_I2C: Routines to communicate with ATmega328P&#39;s hardware I2C port.
#define LTC24XX_EZ_MULTI_SLOW
float LTC24XX_diff_code_to_voltage(int32_t adc_code, float vref)
Calculates the voltage corresponding to an ADC code, given the reference voltage. ...
#define CAL_JUMPER
void quikeval_SPI_connect()
Connect SPI pins to QuikEval connector through the Linduino MUX. This will disconnect I2C...
Definition: LT_SPI.cpp:138
int8_t spi_read(int8_t data)
The data byte to be written.
Definition: LT_SPI.cpp:189
static float type_K_V2C(float tc_voltage, float cj_voltage)
#define CH0_1
const int16_t E
union @1 ptat_cal
#define QUIKEVAL_CS
QuikEval CS pin (SPI chip select on QuikEval connector pin 6) connects to Arduino SS pin...
Definition: Linduino.h:57
static uint32_t adc_code
Definition: DC2071AA.ino:113
#define LTC24XX_EZ_MULTI_R55
#define R55
#define LTC24XX_EZ_MULTI_VIN