Thermal Imaging

This experiment is different from most of my experiments because it uses 20 diodes instead of 20 LEDs. The background for the experiment is that I found some web pages that state that the forward voltage of 1N4148 diodes will vary by approximately 2mV per Kelvin. Of course I had to try this. A quick check with a datasheet and then with a multimeter confirmed that this effect is real. But if it works with 1 diode then twenty would be better, right? If I arrange 20 diodes in a row I will get a very crude 1 dimensional 20 pixel thermal imaging device.

Since I need to measure forward voltage and because the Arduino does not have 20 analog channels I need some kind of multiplexing. Thus I settled for the following circuit.

Thermal Imaging Hack Schematic

As you might have already noticed this looks suspiciously similar to the Blinkenlight circuit. This is because I am going to hack my own kit to fit this setup. Thus I will get a professional layout instead of the typical proto board look. The next picture shows my desired target layout.

Thermal Imaging Hack Layout

Clearly this is almost a Blinkenlight layout. In order to adapt this to my new needs I used my mini drill to cut the traces at just 4 points.

Thermal Imaging Hack Back Cut

The back side cut is the most critical. It has to be wide enough to really cut through the trace. In my first attempt it was not deep enough. The remainder was so thin that I did not see it with the naked eye but still it botched my circuit. So it is a good idea to check this with a multimeter.

Thermal Imaging Hack Front Cuts

The front cuts are not so critical because there is not so much copper around. However they will stay visible so they need somewhat clean work.

In order to get tidy wiring I used 2 pieces of proto board soldered on top of each other. The result is now some kind of a 4 layer PCB plus 4 jumper wires.

Thermal Imaging Hack Back

Soldering the back side was definitely the painful part. In order to prevent accidental shorts I have added some Kapton tape to the “inner” proto board.

Inner Board With Kapton Tape

The easier part was the front. Soldering 1206 size SMD parts is actually very easy to do. The hardest part is dropping none of them to the floor. Here are my results soldering the front.

Thermal Imaging Hack Front

This looks almost professional. Definitely better than some piece of prototyping board. I am very satisfied with how this looks. Unfortunately the back side extends to far and thus this setup does not fit onto a standard Arduino if ISP headers are mounted. All of my Arduinos have mounted ISP headers though. I fixed this by putting a “raiser” board onto one of my Arduinos.

The sketch for the Arduino is pretty simple. I just multiplex the diodes by toggling the digital pins. Each digital pin enables or disables 5 diodes which are then evaluated through the analog ports. Notice that I get some small forward current for each diode by enabling the pull up resistors for the analog pins. Since the forward voltage is well below 1V I also switch the analog reference to internal. That is to 1.1V. This gives roughly 5 times better voltage resolution.

The data is send in rows, one per digital pin. There is no specific reason for that other than it helps me searching for issues with the electrical connections.

const uint16_t AnalogPin[] = {A1, A2, A3, A4, A5};
const uint16_t DigitalPin[] = {5, 6, 7, 8};

const uint8_t AnalogPins = sizeof(AnalogPin)/sizeof(AnalogPin[0]);
const uint8_t DigitalPins = sizeof(DigitalPin)/sizeof(DigitalPin[0]);

const long int SerialSpeed = 115200;

// how many microseconds to wait before starting to sample after a row was changed
const int SampleDelay = 1000;

// how many samples to add up
// notice that Oversamples 2^15/2^10 = 2^5 = 32
// this is because we will not average inside the controller --> output is scaled by Oversamples
// in theory oversampling could be achieved in software outside the controller,
// however this way we reduce traffic on the serial connection, hence we need less bandwidth for data transfer
const int Oversamples = 10;


void setup() {
	Serial.begin(SerialSpeed);
	Serial.print("AnalogPins: ");
	Serial.println(AnalogPins);
	Serial.print("DigitalPins: ");
	Serial.println(DigitalPins);
	Serial.print("Oversamples: ");
	Serial.println(Oversamples);

	// INTERNAL1V1 = 2
	analogReference(2);

	// enable analog pins pullups
	for (uint8_t a=0; a < AnalogPins; ++a) {
		pinMode(AnalogPin[a], INPUT);
		digitalWrite(AnalogPin[a], HIGH);
	}

	// default all outputs to high (= do not measure)
	for (uint8_t d=0; d < DigitalPins; ++d) {
		pinMode(DigitalPin[d], OUTPUT);
		digitalWrite(DigitalPin[d], HIGH);
	}
}


void loop() {
	static int buffer[DigitalPins][AnalogPins];

	// initialize buffer with 0
	for (uint8_t d=0; d < DigitalPins; ++d) {
		for (uint8_t a=0; a < AnalogPins; ++a) {
			buffer[d][a] = 0;
		}
	}

	// sample
	for (uint8_t s=0; s < Oversamples; ++s) {
		for (uint8_t d=0; d < DigitalPins; ++d) {
			// pull pin low to enable measurement
			digitalWrite(DigitalPin[d], LOW);
			delayMicroseconds(SampleDelay);

			for (int a=0; a < AnalogPins; ++a) {
				buffer[d][a] += analogRead(AnalogPin[a]);
			}
			digitalWrite(DigitalPin[d], HIGH);
		}
	}

	// output result
	for (uint8_t d=0; d < DigitalPins; ++d) {
		Serial.print(d);
		for (int a=0; a < AnalogPins; ++a) {
			Serial.print(", ");
			Serial.print(buffer[d][a]);
		}
		Serial.println();
	}
}

The python script will trigger a reset to enforce that it will be in sync with the Arduino sketch. It will collect some samples to compute a baseline. This is kind of a very crude auto calibration. Of course the whole thing could be calibrated for quantitative measurements but I am interested in getting pictures. So as I need only qualitative outputs I settled for this simpler approach. The output is rendered into some scalable vector graphics file in order to enable rendering this into some internet page. Also rendering SVG is so simple with Python that I did not bother to try some sophisticated plotting package.

#!/usr/bin/python
# -*- coding: utf-8 -*-

header_lines = 4
rows   = 4
colums = 5

port = "/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7004mNZ-if00-port0"

baudrate = 115200

tmpfile = './graph.svg~'
outfile = './graph.svg'

width    = 10
separate = 2
offset   = 5700
crop     = 200
scale    = 1

baseline_samples = 10
baseline = [0]

import serial
import time
import sys
import shutil
from pysvg.structure import *
from pysvg.builders import *
#from math import *


if len(sys.argv) == 3:
	ser = serial.Serial(sys.argv[1], sys.argv[2])
else:
	print "# Please specify a port and a baudrate"
	print "# using hard coded defaults " + port + " " + str(baudrate)
	ser = serial.Serial(port, baudrate)


# we rely on the fact that the Arduino will reset once serial reading starts
# hence we know that the first thing that we will see is the header information
ser.setDTR(1)
time.sleep(0.5)
ser.setDTR(0)

# ignore header information
for x in range(0, header_lines):
	print ser.readline()

print "computing baseline"
baseline = [0.0 for dummy in range(0, rows*colums)]

for count in range(0, baseline_samples):
	for row in range(0, rows):
		content = [int(line) for line in ser.readline().split(',')[1:]]
		if len(content) >= colums:
			for col in range(0, colums):
				value = content[col]* scale
				baseline[col+row*colums]+= value

baseline = [int(value / baseline_samples) for value in baseline]
print baseline

print "standard sampling"

while 1:
	f = open(tmpfile, 'wb')
	s=svg(height="100%", width="100%")
	s.set_viewBox("0 -100 500 200")  # min_x, min_y, width, height

	for y in range(-9,10):
		s.addElement(ShapeBuilder().createLine(0, 10*y, 1000, 10*y, strokewidth=0.5, stroke='grey'))
	s.addElement(ShapeBuilder().createLine(0, 0, 1000, 0, strokewidth=1, stroke='black'))

	for row in range(0, rows):
		content = [int(line) for line in ser.readline().split(',')[1:]]
		if len(content) >= colums:
			for col in range(0, colums):
				value = int(max(min(content[col]-baseline[col+row*colums], crop), -crop) * scale)

				s.addElement(ShapeBuilder().createLine(int(450-(1+col+row*colums)*(width+separate)), 0, 450-int((1+col+row*colums)*(width+separate)), value, strokewidth=width, stroke='red'))

	f.write(s.getXML())
	f.close()
	shutil.move(tmpfile, outfile)

For testing I needed something to tell my browser to reread the SVG file over and over again. So this tiny piece of HTML tells it to refresh once per second.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Titel</title>
<meta http-equiv="refresh" content="1; ./graph.html">
</head>
<body>
<embed src="graph.svg" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" />
</body>
</html>

So does it really work? Yes it does! Have a look at the video. Notice that the blue thingy is 30-40K below room temperature. Thus the temperature drops are much larger than the temperature increases I get from touching the diodes with my fingers.

One final word of warning. If you copy this hack, please ensure that you connect the shield to your Arduino only after uploading the sketch. If there is some other sketch running that sets the digital pins to low and the analog pins to high the diodes will more or less short circuit these pins. Chances are that this will cause the Arduino to protect the resetable fuse by letting go some magic smoke.

3 Responses to Thermal Imaging

  1. lktromp says:

    I would recommend using discrete resistors to bias the diodes, instead of the internal pull-up resistor. Not only will this protect your diodes against accidentally burning out a diode, as mentioned in your post, it will also increase performance. Is you can see on chapter 318 of the Atmega328 datasheet the value of the pull-up resistor is between 20K and 50K. This means different diodes can have different forward currents, and thus forward voltage. Did you do any kind of measurements on this effect?

  2. Yes, it is between 20 and 50k. However it does not wildly fluctuate or vary a lot. You are right about the added protection but my goal was to keep the parts count as low as possible. Right now I am only interested in qualitative measurements, so I ignored the impact of the resistors. I did not investigate this in any detail. The impact can be assessed without measurement by looking at the standard models, e.g. the Shockley model http://en.wikipedia.org/wiki/Diode_modelling

  3. Conundrum says:

    Small diodes can also be obtained from any old broken LCD TV power supply.
    These are typically discarded when replaced, and have between 20 and 30 identical SMD diodes on the solder side.
    Probably 1N4148s judging by the voltage drop, but they exhibit similar thermal sensitivity.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s