Introduction: Heat-Seeking Desk Fan (using Arduino)

Summary: The following is a step by step guide on how to make a Heat-Seeking Desk Fan from an Arduino microcontroller, a computer fan, a servo, and an infrared temperature sensor. The device scans the room periodically, automatically pointing itself in the hottest direction (ideally towards a person). This is a working proof-of-concept model, but it could be easily scaled up should the investment seem worthwhile.

I built this as a project for a college electronics course and had originally planned to include a clap sensor to activate the scan, but didn't have enough time or money. I'll provide an update should I decide to add it in the future. 

The concept, design, and code is mine, except for the temp sensor communication code submitted by Sensorjunkie.

Cost: $70 (including Arduino Uno)

Difficulty: Basic Arduino experience is essential. Some of the electronics are not easy to troubleshoot should you run into problems.

Video:

Step 1: Materials

Summary: The required stuff. I've provided links for all the specific parts which cannot be substituted. Everything else you're free to pull from anywhere. Seriously, get creative. Your options will be more clear as you continue reading.

Electronics:
-- Infrared Thermometer MLX90614
http://www.sparkfun.com/products/9570
-- Large Servo Motor
http://www.sparkfun.com/products/9064
-- Arduino Uno Microcontroller
http://arduino.cc/
-- Computer Fan 12V DC
-- 12V DC Transformer

Hardware:
-- Through-hole circuit board
-- Baseplate (6x8in material of your choosing)
-- Nuts and Bolts
-- Various Adhesives
-- Wire
-- Solder

Tools:
-- Soldering Iron
-- Hot Glue Gun
-- Drill Press






Step 2: Wiring

Summary: Geting everything wired up and running.

Temperature Sensor: 
Cut off a small piece of through-hole circuit board just large enough to fit the temperature sensor leads and four wires. Solder the leads and the wires onto the board, leaving about 7in of wire to allow the fan to rotate. The more flexible the wire, the better your life will be.The servo will yank things apart if you're not careful (as you can clearly see in my video).

The wiring is critical so follow the diagram on the data sheet. The SDA (data) and SCL (clock) go to the A4 and A5 analog inputs, respectively, on the Arduino. This particular device runs on 3V, so Vdd can go straight to the 3.3V output pin while GND goes to the obvious.

Servo:
I'm a little disappointed to say that the servo draws too much current for the Arduino. My model provides 5V to the servo with an external voltage supply. If I come up with simple and cheap circuit solution, I'll post it here.

Orange goes to Pin 9, Red to 5V, and Brown to GND.

Fan:
I found a 12V DC transformer lying around, so I soldered it straight to the leads of the fan, including a switch on one of the wires for convenience.  

Step 3: Assembly

Summary: Putting it all together.

1) Attach the fan to the fan to the servo however you can. The servo comes with several different snap-on heads, so you have a variety of options.  I had access to a machine shop, so I milled a small piece of acrylic to interface with the hardware holes on the fan and tapped two holes in the piece to attach to the small servo disk with some screws. Less involved methods: hot glue, zip ties, wire, string, etc.

2) Attach the temperature sensor to the fan using hot glue. The direction of the sensor is critical for proper operationl so make sure to set it straight out  from the fan before the glue hardens.

3) Attach the servo to the baseplate. I would ideally find some nuts and bolts that fit the pre-made holes on the servo and drill out the baseplate. However, right now I've got electrical tape on there. Some less orthodox methods: chewing gum, shoe laces, etc.

4) Mount the Arduino to the baseplate. You can use offset screws, or just let it hang out on your baseplate. If you're anything like me, you'll probably want to use it for another project soon enough.

Step 4: Communicating With the Temperature Sensor

Summary: The infrared temperature sensor is an I2C component. Normally this just means calling on a few commands from the Arduino "Wire" library to get data, but unfortunately, this temperature sensor requires some particular timing adjustments which are not possible using the Wire library. The code I use involves a custom "i2cmaster" library, which you must import according to the following steps. Do some additional research if you're having trouble finding the libraries folder on your particular OS.

Source: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1214872633


1) Importing the Custom "i2cmaster" Library:

A) Go to:

homepage.hispeed.ch/peterfleury/avr-software.html

and download the i2cmaster.zip

Make a folder in /{arduino root}/hardware/libraries and extract the
i2cmaster.h and twimaster.c files. Now rename the .c file of twimaster to .cpp (MAKE SURE TO RENAME AND PUT THESE FILES IN THE RIGHT LOCATION I.E. IN THE LIBRARIES FOLDER OF THE ARDUINO CODE)

Make sure you restart if you load a new library into it so it can be found when it is called.

B) Now you need to modify twimaster.c
Open it in a simple text editor and change the following if you are using an Arduino Uno

Edit the twimaster.c to reflect the 16MHz clock, and change the bus frequency to 50Khz by changing the code at the beginning to:

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

/* I2C clock in Hz */
#define SCL_CLOCK 50000L



2) Testing the Temperature Sensor:
Here's some test code which prints to the serial monitor the temperature in Celsius to two-digits. If it works, you've got your sensor hooked up properly and the library properly imported.


#include

void setup()
{
Serial.begin(9600);
i2c_init(); //Initialise the i2c bus
PORTC = (1 << PORTC4) | (1 << PORTC5);//enable pullups
}

void loop()
{
int dev = 0x5A<<1;
int data_low = 0;
int data_high = 0;
int pec = 0;
i2c_start_wait(dev+I2C_WRITE);
i2c_write(0x07);


i2c_rep_start(dev+I2C_READ);
data_low = i2c_readAck(); //Read 1 byte and then send ack
data_high = i2c_readAck(); //Read 1 byte and then send ack
pec = i2c_readNak();
i2c_stop();

//This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
double tempFactor = 0.02; // 0.02 degrees per LSB
double tempData = 0x0000;
int frac;

// This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
tempData = (double)(((data_high & 0x007F) << 8) + data_low);
tempData = (tempData * tempFactor)-0.01;
tempData = tempData - 273.15;
Serial.print((int)tempData); //Print temp in degrees C to serial
Serial.print(".");
tempData=tempData-(int)tempData;
frac=tempData*100;
Serial.println(frac);
delay(500); 
}

Step 5: The Code

Summary: Hours of hard work and frustration condensed to a single text file. This is the final (mostly) working sketch, written specifically with an Arduino Uno in mind. 

servoSweep() controls the scan and data storage
tempRead() includes all the i2c nonsense and retrieves the the data from the temperature sensor
findHot() returns the servo to the position with the hottest temperature

This is my first attempt at coding Arduino, so if I've made things egregiously overcomplicated or clumsy, feel free to let me know. The first iteration works great, but after that the Servo and i2cmaster libraries conflict and the servo goes a little nuts. If anyone solves it, contact me and I'll update the code.


//**TrackFan**//
//Code and concept by ePums
//tempRead code by Dave Eaton and SensorJunkie

#include <Servo.h>
#include <i2cmaster.h>

Servo mrservo;

int j = 0;
int pos;
int posVals[]= {
  0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120
};
double tempVals[12];
int hotPos;

void tempRead(){

  //Define function for reading the temperature from the IR Sensor.
  int dev = 0x5A<<1;
  int data_low = 0;
  int data_high = 0;
  int pec = 0;
  i2c_start_wait(dev+I2C_WRITE);
  i2c_write(0x07);


  i2c_rep_start(dev+I2C_READ);
  data_low = i2c_readAck(); //Read 1 byte and then send ack
  data_high = i2c_readAck(); //Read 1 byte and then send ack
  pec = i2c_readNak();
  i2c_stop();

  //This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
  double tempFactor = 0.02; // 0.02 degrees per LSB
  double tempData = 0x0000;
  int frac;

  // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
  tempData = (double)(((data_high & 0x007F) << 8) + data_low);
  tempData = (tempData * tempFactor)-0.01;
  tempData = tempData - 273.15;

  tempVals[j] = tempData;
  j+=1;
}


void servoSweep(){
  Serial.println("servoSweep initialize!");
  j = 0;
  pos = 0;
  //reset pos and temp indices
  delay(500);
  Serial.println("Positions:");
  //return servo to initial position
  for (pos = 0; pos < 130; pos +=10)
  {
    mrservo.write(pos);
    //move servo 10 degrees
    delay(200);
    //allow sensor to settle
    tempRead();
    Serial.println(pos);
    //read and store temp data
    delay(300);
  }
}

void findHot(){
  Serial.println ("findHot started!");
  //find highest temperature, move servo to corresponding position
  int i = 0;
  //local search index
  int q = 0;
  double hotTemp = tempVals[0];
  //the position data associated with the highest temperature in a given sweep
  for (i=0; i < 13; i+=1) {
    if (tempVals[i] >= hotTemp) {
      hotTemp =tempVals[i];
      Serial.print("#" + String(i) + " hot Temp: ");
      Serial.println(hotTemp);
      delay(50);
      q = i;
      //retrieve index for highest temp value in tempVals array
    }
  }
  hotPos = posVals[q];
  Serial.println("hotPos: ");
  Serial.print(hotPos);

  //retrieve the corresponding pos value from posVals index
  if (mrservo.attached()) {
    Serial.println("this is reading output right now");
  }
  delay(1000);
  mrservo.detach();
  mrservo.attach(3);
  mrservo.write(hotPos);
  delay(1000);
  //move the servo
  Serial.println("Returning to hotPos");


  /////////**DELAY BETWEEN SCANS**//////////////// 
  delay(10000);

  i=0;
}


void setup(){
  Serial.begin(9600);
  i2c_init();
  PORTC = (1 << PORTC4) | (1 << PORTC5);
}

void loop(){
  mrservo.attach(3);
  delay(1000);
  mrservo.write(0);
  i2c_init();
  servoSweep();
  Serial.println("Temperatures!");
  for (int n=0; n < 13; n += 1) {
    Serial.println(tempVals[n]);
    delay(50);
  }
  findHot();

  mrservo.detach();
  delay(2000);
}

Step 6: Calibration

Summary: Customizing the fan for your personal use.

The default scan range is 120 degrees. Feel free to change this if you're confident with Arduino code. Make sure the fan is attached to the servo such that the scan covers the area you are interested in.

Also, Feel free to play around with the delay between scans until you have something that works best for you.

Step 7: THANKS!

Thanks for reading! I hope this has been interesting and enlightening in some way, and best of luck if you decide to build it! It really is fun.

THE END