Tuesday, March 5, 2013

Infrared Fan Speed Control


This project demonstrates using an infrared light beam to measure the speed of a fan, and PWM to adjust the speed to a preset value.  I used a small 12 Vdc fan from a PC CPU heat sink.  It will run on 5V, somewhat slow, and drawing only about 40 mA, perfect for this project.  Although an Arduino digital output could safely drive the fan, I decided to play it safe and use a transistor.  Here's my schematic, created with TinyCAD:



The 10K Ohm pot sets the target speed.  The PWM output on pin D5 drives the transistor to regulate the motor speed.  The fan blades interrupt the infrared beam, and that generates interrupts via pin D3.  Here's the sketch:


/*
 IR_MotorSpeed.pde miketranch comcast.net V0.4 Feb 8, 2013
 10K pot sets desired speed 
 IR detector measures fan speed
 adjusts PWM voltage to regulate speed
 Arduino UNO pins used:
   D3  IR detector, interrupt 1
   D5  PWM output drives motor
   A0  10K pot
*/

const int potPin = 0;    // 10K pot 5V to ground, wiper to pin A0 
const int PWMLED = 5;    // PWM output to transistor to motor
const int IR_Pin = 3;    // Hall Sensor Output on Pin 3
const int IR_Int = 1;    // Int #1 on Pin 3
volatile int count = 0;  // count interrupts from IR Sensor

void setup()
{
  pinMode(IR_Pin, INPUT_PULLUP); // open collector IR detector
  Serial.begin(9600);
  Serial.println("\n\nInfrared Fan Motor Speed Control");
}

void loop() { 
  // static variables remember their values
  static int PWMval; // latest attempt to set target speed 
  
  // regular variables are initialized each time loop() executes
  int potVal = analogRead(potPin); // read the voltage on the pot
  // map to desired speed range  
  int SetSpeed = map(potVal, 0, 1023, 50, 400); 
  
  if (count == 0) {      // motor stopped, kick start it
    PWMval = 254;        // full voltage and power
    Serial.println("Full Power Start"); 
  } else {               // adjust speed
    float delta = (SetSpeed - count); // speed error
    if (delta > 0 )                   // too slow, increase voltage
      PWMval = PWMval + sqrt(delta);  // by square root of error
    else                              // too fast, decrease voltage 
      PWMval = PWMval - sqrt(-delta); // by square root of error
  } 
  
  PWMval = constrain(PWMval, 0, 255); // PWM must be 0 to 255
  
  Serial.print("Set speed: "); Serial.print(SetSpeed);
  Serial.print(" PWM: ");      Serial.print(PWMval);
  
  analogWrite(PWMLED, PWMval);  // set the motor voltage level
  delay(1000);       // give motor time to settle to new speed
  
  count = 0;
  attachInterrupt(IR_Int, IR_ISR, CHANGE); 
  delay(1000);               // measure for 1 second
  detachInterrupt(IR_Int);   // disable interrupt 
  
  Serial.print(" Speed: "); Serial.println(count);
}
     
void IR_ISR() {
  count++;  
}


It was fun working out the math to get this sketch working well with the hardware.  The interrupt is enabled for 1 second, and counts both edges of each blade, because of the CHANGE keyword.  I empirically determined the motor could be slowed to about 40 interrupts per second, or sped up to about 420 interrupts per second.  The map function converts the 10K pot setting to a SetSpeed value in the range of 50 to 400.

If the motor stalls, the PWMval is set to 254, essentially full power, but still giving pulses that are convenient for triggering a scope.  Otherwise, the error computed as SetSpeed minus the measured speed.  I tried adjusting PWMval up or down by the error value, but the motor speed didn't settle very well.  Then I realized that the PWMval is controlling the voltage, but the motor speed is probably more directly related to power.  So I tried adjusting PWMval by the square root of the error, and that works better.  Here's a sample output from the serial monitor:


Infrared Fan Motor Speed Control
Full Power Start
Set speed: 228 PWM: 254 Speed: 299
Set speed: 228 PWM: 245 Speed: 420
Set speed: 228 PWM: 231 Speed: 423
Set speed: 228 PWM: 217 Speed: 405
Set speed: 228 PWM: 203 Speed: 383
Set speed: 228 PWM: 190 Speed: 360
Set speed: 228 PWM: 178 Speed: 337
Set speed: 228 PWM: 167 Speed: 315
Set speed: 228 PWM: 157 Speed: 295
Set speed: 228 PWM: 148 Speed: 279
Set speed: 228 PWM: 140 Speed: 261
Set speed: 228 PWM: 134 Speed: 245
Set speed: 228 PWM: 129 Speed: 232
Set speed: 228 PWM: 127 Speed: 223
Set speed: 228 PWM: 129 Speed: 221
Set speed: 228 PWM: 131 Speed: 226
Set speed: 228 PWM: 132 Speed: 229
Set speed: 228 PWM: 131 Speed: 229
Set speed: 228 PWM: 130 Speed: 227
Set speed: 228 PWM: 131 Speed: 226
Set speed: 228 PWM: 132 Speed: 228
Set speed: 228 PWM: 132 Speed: 229
Set speed: 228 PWM: 131 Speed: 228
Set speed: 228 PWM: 131 Speed: 227
Set speed: 228 PWM: 132 Speed: 229
Set speed: 228 PWM: 131 Speed: 228
Set speed: 228 PWM: 131 Speed: 228