DIY Thermal Camera

A $50 thermal camera using a non-contact thermometer, 2 servos, and an Arduino

Similar projects worth following
By combining a non-contact infrared laser thermometer with a servo gimbal, I was able to create a high resolution thermal camera on the cheap.
An Arduino communicates with the thermometer over a serial connection using an unpopulated portion of its circuit board, and controls pan and tilt servos. The measurements are fed to a nearby PC via USB serial connection, saved one by one to an image buffer, and drawn on screen as a full image. Exposures are taking about 30 minutes to capture, but I'm working to speed this up.
Future work will involve interactive selection of scan area, temperature range settings, and the addition of a webcam for automated overlay of temperature data and color imagery.

This is the thermometer I purchased, the heart of the system:

  • 1 × Pittsburgh Infrared Laser Thermometer ($18.99 via Harbor Freight Item #9398) a non-contact infrared thermometer with a built in header that speaks serial
  • 1 × Mallofusa Servo Gimbal ($19.54 via Amazon) Servos with mounts that control the pan and tilt of the thermometer
  • 1 × Aduino Uno or similar ($5-$25, but I already had one on hand) Microcontroller to bridge a connection between a computer and the thermometer
  • 1 × Computer and USB cable (could use any PC or a Raspberry Pi that can run Processing sketches) A host computer to run the Processing Sketch that sets the servo limits, triggers the scanning to begin, and builds the scanned visual image
  • 1 × Assorted wires and connectors ($0 out of the parts bin if you're like me) to connect the thermometer and servos to the Arduino. I used cables with header pins and sockets to make everything changeable as I prototype
  • 1 × Webcam for future phases of project ($5-$50 depending on resolution and options) Later I would like to add a camera to capture a visible light image of the scanned area to automatically align and overlay the thermal image onto. For now I'm using my smartphone and Photoshop to do this manually.

  • Pan Tilt Servo Control

    Michael Shaub03/26/2017 at 16:08 0 comments

    The gimbal was pretty quick to assemble, though the order of connections was critical and I found I had to take it apart and reassemble a couple times before finishing.

    Connecting the servos to an Arduino requires some changes to the wiring. The critical thing is to power the servos with an external source, if you don't the Arduino will brown out when trying to move both simultaneously. I used a 6V external power supply so the servos wouldn't be powered through the USB port.

    The pan and tilt motion resulted in a way wider angle than I needed. This could be useful when the thermometer needs to be placed close to the target, but lots more math would be required to account for the angle of motion. From a distance the angular motion can be translated linearly without much distortion.

    I considered modifying the servos in some way, probably not gearing but maybe the resistance values of the potentiometers inside so the feedback loop would limit the rotation to a more narrow range. I needed higher torque on another project and used these external servo gearboxes from servo city, those could work but would make the gimbal much larger and more complex.

    But, luckily I discovered that servos can be controlled from an Arduino with Microseconds instead of angular measurements. Even at the small angles I wanted the servos to move, microsecond signals allow for 500+ steps of movement. This is possible in the standard library, but I ultimately elected to use the alternate VarSpeed library so I could set acceleration speeds when needed.

    This is the code I used to test drawing a frame around the area to be scanned, as a preview before beginning the scan.

    // Michael Shaub 1/18/2017
    // Based on Sweep by BARRAGAN  
    #include <Servo.h> 
    Servo myservoTilt;  // create servo object to control a servo 
    Servo myservoPan;  // create servo object to control a servo 
                    // a maximum of eight servo objects can be created 
    int posTilt = 1500;    // variable to store the servo position 
    int posPan = 1500;    // variable to store the servo position
    int tiltLimitUpper = 1350; //2000;
    int tiltLimitLower = 950; //500;
    int panLimitUpper = 1750; //2300;
    int panLimitLower = 1350; //700;
    boolean countDown = true;
    int frameCase = 0; //state of the frame sequence
    //0=Upper Left Corner, 1=Upper Right Corner
    //2=Lower Right Corner, 3=Lower Left Corner
    void setup() 
      myservoTilt.attach(9, tiltLimitLower, tiltLimitUpper);  // attaches the servo on pin 9 to the servo object 
      myservoPan.attach(10, panLimitLower, panLimitUpper);  // attaches the servo on pin 9 to the servo object 
    void loop() 
    void frame(){
      case 1: //Upper Right Corner
          myservoTilt.writeMicroseconds(posTilt);  // tell servo to go to position in variable 'posTilt'
      case 2: //Lower Right Corner
          myservoPan.writeMicroseconds(posPan);  // tell servo to go to position in variable 'posPan'
      case 3: //Lower Left Corner
          myservoTilt.writeMicroseconds(posTilt);  // tell servo to go to position in variable 'posTilt'
      default: //Upper Left Corner
          myservoPan.writeMicroseconds(posPan);  // tell servo to go to position in variable 'posPan'

  • Reading the thermometer with an Arduino

    Michael Shaub03/26/2017 at 15:00 0 comments

    According to this Instructable the thermometer's pins should connect to an Arduino like this:

    • pin D (IR data) to pin 12
    • pin C (IR clock) to pin 2
    • pin V (IR power) to 3.3V power
    • pin G (IR ground) to ground

    Here's the sample code I tried first to get some measurements streaming in:

    byte n = 0;                            // Interrupt Bit Count        
    volatile byte pos = 0;            // Values Position Count
    volatile byte values[5] = {
      0,0,0,0,0};                                                   // Values to be stored by sensor
    byte cbit = 0;                    // Current bit read in
    boolean irFlag = false;           // Flag to indicate IR reading has been made
    boolean ambFlag = false;          // Flag to indicate ambient temp reading has been made
    byte irValues[5] = {
      0,0,0,0,0};                                      // Variable to store IR readings
    byte ambValues[5] = {
      0,0,0,0,0};                                           // Variable to store Ambient readings
    const int len = 5;                        // Length of values array
    const int clkPin = 2;                           // Pins
    const int dataPin = 12;
    const int actionPin = 5;
    void setup(){
      pinMode(clkPin, INPUT);         // Initialize pins
      pinMode(dataPin, INPUT);
      pinMode(actionPin, OUTPUT);
      digitalWrite(clkPin, HIGH);
      digitalWrite(dataPin, HIGH);
      digitalWrite(actionPin, HIGH);
      Serial.println("Type to Start...");                   // Wait for input to start
      Serial.println("IR (C), Ambient (C), Time Since Start (ms)");
      attachInterrupt(1,tn9Data,FALLING);             // Interrupt
      digitalWrite(actionPin,LOW);          // Make sensor start sending data
    void loop(){
      if(pos == len && values[0] == 0x4C){            // If sensor has sent IR packet...
        for(int i = 0; i < len; i++){                     // Store values to irValues
          irValues[i] = values[i];
        irFlag = true;                               // Indicate IR reading
        pos = 0;
        digitalWrite(actionPin,LOW);             // Make sensor start sending data
      if(pos == len && values[0] == 0x66){          // If sensor has sent ambient packet...
        for(int i = 0; i < len; i++){                   // Store values to ambValues
          ambValues[i] = values[i];
        ambFlag = true;                     // Indicate Ambient reading
        pos = 0;
        digitalWrite(actionPin,LOW);           // Make sensor start sending data    
      if(pos == len && values[0] == 0x53){         // If sensor has sent junk packet
        pos = 0;
        digitalWrite(actionPin,LOW);          // Make sensor start sending data   
      if(irFlag && ambFlag){        // If successful IR and Ambient reading...
        digitalWrite(actionPin,HIGH);   // Make sensor stop sending data.  Because Timing is weird, I want to ensure the interrupts do not happen during this section.   
        word tempword = 0;        // Next 4 lines isolate temperature component of values
        tempword = tempword | irValues[1];
        tempword = tempword << 8;
        tempword = tempword | irValues[2];
        if(tn9Check(irValues)){       // If checksum is valid, print IR temperature
          //Serial.print("IR = ");
          Serial.print(int(tempword)/16.0 - 273.15);
          Serial.print(", ");       
        else{                 // If checksum isn't valid, print impossible temp
          //Serial.print("IR = ");
          Serial.print("-273.15, "); 
        tempword = 0;         // Isolate temperature component again for ambient
        tempword = tempword | ambValues[1];
        tempword = tempword << 8;
        tempword = tempword | ambValues[2];
        if(tn9Check(ambValues)){        // If checksum is valid, print ambient temperature
          //Serial.print("Amb = ");
          Serial.print(int(tempword)/16.0 - 273.15);        
        else{           // If checksum isn't valid, print impossible temp
          //Serial.print("Amb = ");
        irFlag = false;         // Reset flags
        ambFlag = false;
        Serial.print(", ");
        Serial.println(millis());                           // Print time for logging purposes
        delay(2000);               // Simulate other sensors or code
        digitalWrite(actionPin,LOW);               // Make sensor start sending data  
    void tn9Data(){           // Interrupt Function
      cbit =  digitalRead(dataPin);     // Read bit
      if(pos >= len) pos = 0;               // Keep index below 5
      values[pos] = (values[pos] << 1) | cbit;          // Store to values
      n++;              // Increment bit count
      if(n == 8){           // Increment position count based on bits read in...
    Read more »

  • Modification of Infrared Thermometer

    Michael Shaub03/26/2017 at 14:47 0 comments

    Luckily, the thermometer's circuit board has an unpopulated header that's ready for hacking. I wanted to keep my setup flexible so I used an old cable with the right number of wires and soldered a set of header pins onto the board. By making a hole in the thermometer case the cable can be removed anytime.

    According to this Instructable the pin out is:
    • pin D: IR data
    • pin C: IR clock
    • pin V: IR power (3.3V), and laser pointer power can be connected here too
    • pin G: IR ground

    (photos to come)

View all 3 project logs

Enjoy this project?



gwfami wrote 03/26/2017 at 16:46 point

Perhaps if you used multiple temp sensors, say 4 at a time, that would significantly reduce your scan time.

  Are you sure? yes | no

Michael Shaub wrote 03/29/2017 at 02:39 point

cool idea. If I can't get this one sensor to scan faster, maybe I'll try more. Thanks

  Are you sure? yes | no

Eric Moyer wrote 03/25/2017 at 16:31 point

Great project.  How large of an area are you scanning in 30 minutes?  Please keep updating this project since it looks awesome.

And if case anyone doesn't know, always wait for a Harbor Freight sale or use a coupon to get their products cheap.

  Are you sure? yes | no

Michael Shaub wrote 03/26/2017 at 13:57 point

Thanks! I scanned an area about 8' square and a resolution of 100x100 in 30 minutes. To do a faster capture, I reduced this to 50x50 but didn't see much detail at all. I already added some code to move to the next pixel as soon as a new temperature is detected, so there's no unnecessary delay. 

I got some great advice at the un-conference to try storing some or all of the temperatures on the arduino and transferirng them in batches to speed up captures. Can't wait to try that too.

  Are you sure? yes | no

Dylan Fahey wrote 03/23/2017 at 14:55 point

During your presentation at the Unconference you mentioned that your biggest expense was the mount. Have you looked at the servo mounts on Aliexpress? There's a design out there that uses the little servos that come in every starter kit that I've gotten in the past for about 60 cents. 

They have beefier ones that resemble the one you were using for about 5 dollars or so. 

  Are you sure? yes | no

Michael Shaub wrote 03/24/2017 at 03:37 point

thanks! I'll check them out for sure.

  Are you sure? yes | no

Mike Szczys wrote 03/22/2017 at 19:01 point

Details! Details! (they all cried) For me the software explanation was the coolest part. You moved moutains to get this level of quality. Do tell.

  Are you sure? yes | no

Adam wrote 03/22/2017 at 17:31 point


  Are you sure? yes | no

Tom Meehan wrote 03/21/2017 at 06:42 point

Your presentation at the Hackaday Unconference in Chicago inspired me to look into focusing methods for IR sensors, which has helped me immensely (I purchased a non-contact temp sensor on Sunday and soldered in headers to start tests - found that the sensor has a fresnel lens to focus the IR heat signal).  Can't wait to further details of your project!!

  Are you sure? yes | no

oshpark wrote 03/20/2017 at 21:38 point

Great project!  Thanks for presenting it at the Hackaday Unconference in Chicago

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates