Hiya pri,
If the budget won't stretch to the thermal imager, then Brad's other suggestion of direct contact sensors are a good plan. There are a range of temperature sensor options, all with their own advantages/disadvantages. For example, an option lending itself to arrays is a device like the **broken link removed**, which can be built into a network with minimum cabling and provides repeatable, 'accurate' measurements. It'll cost a fortune for a large/dense array, however
At the lower cost end are sensors like thermocouples and thermistors, which require (some degree of) analog support electronics, and carry the added burden of requiring calibration. Of the two, I'd strongly recommend the thermistor - as it is (very) cheap and the analog electronics complexities are minimal. There is an abundance of information on the 'net regarding their use (start here:
https://en.wikipedia.org/wiki/Thermistor), and they can be measured simply (and cheaply) just using a $5 multimeter. Their resistance is converted back into a temperature via a model such as the Steinhart-Hart equation (see Wikipedia). When used for body temperature sensing, such models (or even the simpler β-parameter equation) work really well, since the range of temperature values encountered is restricted to a narrow band around 37°C (and you can therefore ignore higher order/unmodelled non-linear effects).
I recently built something like you're after - and while it's not a great example of engineering, it might give you some ideas you can use! The background: A few months ago I had surgery and was paranoid about the wound healing, so I wanted to measure the skin temperature profile in the area over/around the incision. Since I couldn't drive (to buy parts) - that meant using what was littering the home study - and the search turned up three identical thermistors... which was a lot lamer than the "array" hoped for, but it would have to do
For similar reasons (i.e. I had one), I used a dsPIC30F4013 as the microprocessor to drive it all.
(As an aside - you *can* buy commercial dataloggers - such as:
https://www.onsetcomp.com/products/data-loggers/u12-006 which can be used for multipoint temperature measurement [and they use thermistors]).
Because I didn't want to be tethered to a PC for several days, I used the dsPIC's internal memory to store the temperature readings from the sensors. As its memory is severely limited, I restricted the range of values able to be represented to an 8°C band around 37°C, allowing for 0.25°C precision. (The sensors permit much greater precision if you have the external memory capacity to store it). I'd planned to multiplex the dsPIC's analog inputs and switch the supply voltage to banks of thermistors via the digital IO pins, but didn't have the need with only 3 sensors to hand... Here's a sketch of the "commercial concept" (+rough costings) I had in mind (I was immobile, with a week of time to kill
...
What I ended up building was this:
...constructed directly by soldering the parts to the DIP package of the dsPIC, and then potting the whole assembly (including the LiIon battery salvaged from a Bluetooth earpiece) in hot-melt glue:
I then taped the sensors to the outside of the surgical dressing using surgical tape and went about ...not much at all... and hooked myself to the Microchip ICD-2 every 8-ish hours to download the logged data!
For example... (can you see the point when I climbed out of bed?)
...which by post-processing the data (and noting the sensor locations) could be turned into a "heat map". Yes, I realise it's actually quite artificial/pointless with only three sensors, but...
Needless to say, buy the time I'd got all of this working I had pretty much healed (uneventfully) and it was all completely moot! It was a good learning exercise though, and hopefully provides you with a couple of leads to pursue... good luck!
P.S. For anyone who's interested - here's the source code:
(No claims are made as to its elegance!)
Code C - [expand] |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
| // Logger.c JW 11 May 2012
//
// This is a simple application to log three channels of ADC temperature data
// for later retreival via the ICSP port.
//
// The code is targeted for the dsPIC30F4013 using the Microchip C30 compiler
//
// Revision History:
// -----------------
// v1.0 11 May 2012 Initially created.
// This version simply records 340 samples x 3 channels of temperature data every minute,
// and flashes an LED as the buffer approaches full.
//
//
#include <p30f4013.h>
#include <math.h>
#include "eeprom_rw.h"
// Control sampling interval (seconds) + number of samples acquired
#define SAMP_INTERVAL 60
#define MAX_SAMPLES 512
// Configuration bit settings
_FOSC(LPRC)
_FWDT(WDTPSB_16 & WDTPSA_512 & WDT_OFF)
_FBORPOR(PWRT_64 & BORV27 & PBOR_OFF & MCLR_EN)
_FGS(GWRP_OFF & CODE_PROT_OFF);
// Global variables
int secs = 0; // Timing variable
int LEDonEvery = 0; // Variable controlling LED flashing (0 = OFF)
// Pin definitions
#define LED LATBbits.LATB8
#define ANALOG_EN_ LATBbits.LATB5
//
// Private functions...
//
void enable32KHzOsc (void) // The cumbersome way of performing: OSCCONbits.LPOSCEN = 1;
{
asm volatile (" mov #0x0046,w1 " ::: "w1"); // ::: Syntax tells the compiler that w1 has been clobbered
asm volatile (" mov #0x0057,w2 " ::: "w2");
asm volatile (" mov #OSCCONL,w3" ::: "w3"); // Pointer to OSCCONL
asm volatile (" mov #0x02,w0 " ::: "w0"); // Enable 32 KHz oscillator
asm volatile (" mov.b w1,[w3] "); // OSCCONL unlock step 1
asm volatile (" mov.b w2,[w3] "); // OSCCONL unlock step 2
asm volatile (" mov.b w0,[w3] "); // Write #0x02 to OSCCONL
}
void initPIC (void)
{
LED = 0;
ANALOG_EN_ = 1;
TRISBbits.TRISB5 = 0; // Set RB5 as an output for the ANALOG_EN_ drive signal
TRISBbits.TRISB8 = 0; // Set RB8 as an output for the LED
ADPCFGbits.PCFG5 = 1; // Set RB5 as a digital IO pin
ADPCFGbits.PCFG8 = 1; // Set RB8 as a digital IO pin
enable32KHzOsc();
PR1 = 0x8000; // Timer period = 1Hz
IPC0bits.T1IP = 0x7; // Assign priority 7 (highest) to timer interrupt.
IEC0bits.T1IE = 1; // Enable TMR1 interrupt
IFS0bits.T1IF = 0; // Clear the TMR1 interrupt flag
T1CONbits.TCS = 1; // Use LP oscillator as timer clock source
T1CONbits.TGATE = 0; //
T1CONbits.TSYNC = 0; //
T1CONbits.TSIDL = 0; // Enable TMR1 to continue running in sleep mode
T1CONbits.TON = 1; // Start the timer!
ADCON1 = 0x00E4; // ADC auto conversion, auto sampling after conversion
ADCON2 = 0x0410; // Vref = Vcc/GND, perform 5 conversions
ADCON3 = 0x0800; // ADCclk = 2*CPUclk, sampling time = 8 ADCclk cycles
ADCHS = 0; // Manual channel selection N/A - channel scanning used
ADCSSL = 0x001F; // Scan through channels 0-4
ADPCFGbits.PCFG0 = 0; // 2.5V reference input
ADPCFGbits.PCFG1 = 0; // Thermistor 1
ADPCFGbits.PCFG2 = 0; // Thermistor 2
ADPCFGbits.PCFG3 = 0; // Thermistor 3
ADPCFGbits.PCFG4 = 0; // 'Ground' connection
TRISBbits.TRISB0 = 1; // 2.5V reference input
TRISBbits.TRISB1 = 1; // Thermistor 1
TRISBbits.TRISB2 = 1; // Thermistor 2
TRISBbits.TRISB3 = 1; // Thermistor 3
TRISBbits.TRISB4 = 1; // 'Ground' connection
}
double ADC2degrees(int refValue, int zeroVal, int tempValue)
{
double Vcc, i, Vt;
Vcc = 10240.0/(double)(refValue-zeroVal); // Determine supply (=reference) voltage [V]
i = Vcc*(double)(tempValue-zeroVal)*3.5904E-5; // Units: mA
Vt = (double)(tempValue)*Vcc*244.141E-6; // Thermistor voltage
return(1.0/(312.8E-6*log(0.1*(Vcc-Vt)/i) + 3.354E-3) - 273.15);
}
void _ISR _T1Interrupt(void) // Real Time Clock interrupt - executed every second
{
int tVar; // Variable to determine LED flashing sequence
LED = 1; // Ever so briefly blink the LED once per second
// (LED stays on while ISR executes)
IFS0bits.T1IF = 0; // Clear the TMR1 interrupt flag
if (++secs > SAMP_INTERVAL)
secs = 0;
if (LEDonEvery > 0) { // Turn LED on every LEDonEvery seconds
tVar = secs/LEDonEvery;
if (LEDonEvery*tVar == secs)
LED = 1;
else
LED = 0;
}
else
LED = 0;
}
//
// Main program...
//
int main(void)
{
int delayVar, sample = 0;
double Tsensor1, Tsensor2, Tsensor3, delta;
unsigned int t1, d2, d3, tempWord;
initPIC(); // Initialise PIC registers & peripherals
while (sample < MAX_SAMPLES) {
Sleep(); // Lay dormant until time = xx:xx:00
if (secs == 0) {
ANALOG_EN_ = 0; // Supply power to the thermistors & voltage reference
for (delayVar=0; delayVar<500; delayVar++) // Wait for ~40 ms (>> 1 ms)
asm(" NOP ");
IFS0bits.ADIF = 0; // Clear ADC interrupt flag
ADCON1bits.ADON = 1; // Turn the ADC on!
for (delayVar=0; delayVar<10; delayVar++) // Wait for ~1 ms for ADC module to stabilise
asm(" NOP ");
while(!IFS0bits.ADIF); // Wait for conversion to complete
ANALOG_EN_ = 1; // Analog voltage supply off again...
ADCON1bits.ADON = 0; // ...and turn the ADC back off too (saves 60 uA in sleep mode)
Tsensor1 = ADC2degrees(ADCBUF0, ADCBUF4, ADCBUF1); // Convert measurement result to degrees C
Tsensor2 = ADC2degrees(ADCBUF0, ADCBUF4, ADCBUF2);
Tsensor3 = ADC2degrees(ADCBUF0, ADCBUF4, ADCBUF3);
// Create packed data word for writing to EEPROM
// Format: [Temperature 1 - 26.0C (Q4.2) | Temp2-Temp1 (Sign + Q2.2) | Temp3-Temp1 (Sign + Q2.2)]
if ((Tsensor1 < 26.0) || (Tsensor1 > 42.0))
tempWord = 0x03FF; // If data invalid, signify error
else {
t1 = (unsigned int)(4.0*(Tsensor1-26.0)) & 0x3F; // Convert -> fixed point format
delta = 4.0*(Tsensor2-Tsensor1);
if (delta >= 0.0)
d2 = (unsigned int)(delta) & 0x0F;
else
d2 = ((unsigned int)(-delta) & 0x0F) | 0x10;
delta = 4.0*(Tsensor3-Tsensor1);
if (delta >= 0.0)
d3 = (unsigned int)(delta) & 0x0F;
else
d3 = ((unsigned int)(-delta) & 0x0F) | 0x10;
tempWord = (t1 << 10) | (d2 << 5) | d3;
}
WriteWord(sample++, tempWord); // Write word -> EEPROM
// Alert user (me :) if I'm running low on memory
if ((MAX_SAMPLES - sample) < 120)
LEDonEvery = 5; // Flash the LED on for 1s every 5s
if ((MAX_SAMPLES - sample) < 30)
LEDonEvery = 3; // Flash the LED on for 1s every 3s
}
}
LEDonEvery = 2; // Out of memory! Sit blinking furiously...
while(1)
Sleep(); // (Until the battery goes flat!)
} |