Computer controlled electric fan setup for the breadbox American

Hot-rodding the Nash/Rambler 195.6 overhead valve six

24 May 2010

Since I'd ditched the boat anchor generator in favor of a sweet little Mitsubishi 35-ampere alternator, I actually had the juice to run electric fans, and gain a half a precious horsepower in the bargain. Every bit helps in this car!

You can't really appreciate just how small this car is until you attempt to do something like fit electric fans in it. There's simply no room!

But I managed to fit a pair of 11" fans I got from Summit between the radiator and grille, using some simple fabricated brackets. The fans mount to the radiator support at each side, and anchor to the front valance/hood latch brace in the center. I bought the thinnest fans I could find, Summit part number PRO-67012, 2.5" thick.

The controller

I've never been happy with the way aftermarket electric fans behave. The thermostatic switches are hard to set to the right set point, and they run on too long after the car is shut down and cause huge surges and load dumps and are hard on wiring.

Over the last year, contemplating this hot rod engine build, I designed my ideal controller, using an Arduino microcontroller and a couple of solid-state relays (automotive application "high side drivers", VN05) chips. Since I'd ditched the genny I built the controller into the old voltage regulator case and mounted it in it's original location.

The controller uses the Stewart Warner aftermarket electric temperature gauge sender as it's sole source of information on engine state: on/off and temperature. (It would not be hard to make it work with factory on/off type senders and gauge regulators, but I wanted a more accurate controller). Fan motor speed varies slowly over time (as does engine temperature), so there is no huge surge as the fan clicks on. There is a simple calibrated knob on the box, marked in degrees F, that is the "set point"; the temperature at which the thermostat begins to open. From the set point to 15 degrees over the set point, fan speed varies from minimum (a quiet hum) to full blast.

When the car is turned off, the algorithm chooses a "cool down cycle" fan speed based upon the most recent temperature reading. If the engine was below the set point, the fans simply turn off, slowly. Otherwise the fan is set to half the engine-on speed (with a minumum speed) for five minutes, then the fans are again turned off slowly. Quiet, simple minimal stress.

Fan Controller schematic and code

Schematic:

/*

 Smart controller for an automotive cooling fan.
 
 Tom Jennings

 07 Jun 2010 Formalized fan-off-below-setpoint (COLDOFF)
 04 May 2010 fixed calibration, cold-then-off bug 
 29 Mar 2010 working
 25 Feb 2010
 
 based upon WPS/Cars/Fan Controller/fan.c, 27 Apr 2007
 
 This is code for an automotive cooling fan controller that
 adjusts motor speed dynamically to correspond to heat load.
 
 Requires the installation of a Stewart Warner DLX series electric 100 - 280 degree
 electric temperature guage. IMPORTANT NOTE: the sensor/gauge uses
 an internal balanced bridge that makes it insensitive to battery
 voltage; having access to only one leg of the bridge means the
 sensed voltage is proportional to battery voltage. For our purposes
 however it's accurate enough.
 
 NOTE: Could certainly be made to work with the factory make-break
 gauge system, but would require a long time constant filter. Probably
 just making the averaging array large (Nyquist) relative to make/break
 time would work.
 

OPERATING CHARACTERISTICS

 NOTE: names in (PARENS) refer to definitions within the code.
 
 The SW sensor is the sole sensor for this controller, both engine on/off
 and current temperature. There is a single potentiometer, called SET POINT,
 that sets the temperature that the controller begins to run the fan motor.
 The set point pot runs from 160 degrees (CCW) to 205 degrees (CW).
 
 The controller draws 25mA continuously when off. Just FYI.
 
 The controller monitors engine status and temperature continuously.
 The fan is only run when it is needed, and fan speed always changes
 slowly, avoiding large electrical system current excursions and load dumps,
 and overall consumes less energy.
 
 The SET POINT pot sets the temperature at which the controller centers
 its operation. This should be the installed thermostat temperature, more
 or less.
 
 At (COLDOFF) degrees below the set point, the fan operates at its
 lowest possible speed (SLOW) (about 25%). (Below this many fans will stall
 and consume current but not operate.)
 
 Engine temperature above the set point causes the fan speed to increase.
 Fan speed is maximum at (RANGE) degrees over the set point. The fan speed
 varies linearly between the set point and set poin + (RANGE).
 
 
 When the engine is turned off, the controller uses the last-known
 temperature to determine what to do. If the temperature was at or above
 the set point, the fan will run for five minutes after engine-off.
 The fan speed will be (SLOW) (sufficient, because the engine is producing
 no additional heat and water is no longer circulating) unless the
 fan speed had been (FAST), in which case the fan will run (HALF).
 

TEMPERATURE AVERAGING, AND LOOP TIME

 The main loop and all functions are based upon a very slow (ITER)
 loop time; it's a 500lb block of cast iron, temperature changes slowly!
 This code was originally going to use the factory temp sensor but
 they seem to vary too widely and the make-break dash voltage regulator
 is a PITA so I went with the predictably accurate SW system.
 
 There is an array that generates an average over (AVG * ITER) seconds,
 if this were 30 or 45 seconds and had 100 samples it would probably work
 with the factory sensor with no electronic changes.
 
HARDWARE

 As little as possible! An arduino controller, two VN05 (high side driver)
 chips, a fat Shottky diode for commutating the motor, and a resistive divider
 (55% reduction) to scale the sensor to the Arduino's 5VDC in limit.
 
 

 See #define for SLOPE for cal process.

EXTERNAL CONNECTIONS

 GROUND
 BATTERY
 TEMPERATURE SENSOR
 FAN

 
 */


/* Inputs. */
#define CAL 5
#define SENS 3

/* Outputs. */
#define FAN 9
#define LED 10

/* Fundamental loop time, mS. */
#define ITER (1000)

/* Fan runs after ignition off, mS. */
#define COOLTIME (5L*60L*1000L)

/* Full scale temperature; beyond this the
engine is assumed to be off. */
#define FS 340

/* The fan will begin to run at minimum speed at
temperature-COLDOFF degrees. */
#define COLDOFF 5

/* Temperature return value that means car is off. */
#define CAROFF -1

/* Fan speeds. */
#define OFF (0)
#define SLOW (70)
#define HALF (130)
#define FAST (255)

/* Arduino A/D, volts per bit. */
/* Temperature sensor input range, A/D 0 - 1023. */
#define VPB ((float)(5.0/1024))

/* CALIBRATION: The input runs through a voltage divider with a
for a scale factor of 0.55;  9.0V at the sensor is A/D full scale.

From here calculate m and Y intercept:

http://www.mathsisfun.com/straight-line-graph-calculate.html 

X,Y pairs are scaled sensor voltage, temperature.

*/
#define SENSSLOPE -139.4
#define SENSINTERCEPT 726.6


/* Set point pot limits, A/D 0 - 1023. */
#define SETMIN 160
#define SETMAX 205

/* Temperature range over which we servo. */
#define RANGE 15

/* Fan speed rate-of-change, in units per iteration. */
#define FROC 5

unsigned char state = 0; 	/* state machine */
int temp;                       // calculated temperature
int setpt;                      // calculated set point
char engineoff;                 // true when engine is off
int fanspeed;                   // current (changing) fan speed
int fangoal;                    // desired (goal) fan speed
int blinkrate;                  // LED blink rate (fan speed)
char blinker;                   // LED state

unsigned long T, nextT, COOLTIMER, blinkT;

#define AVG 5
int avg[AVG], a;                // average sensor reading junk

#define LOWPASS 7
unsigned char offcount, oncount; 

void setup() {
  analogWrite (FAN, fanspeed= 0); // stop fan immediately.
  pinMode (LED, OUTPUT);
  digitalWrite (LED, 1);          // light LED
  delay (2000);
  Serial.begin (9600);
  Serial.println ("Hello.");
  digitalWrite (LED, 0);
  state= 0;
  engineoff= 1;
}


void loop() {
  int d;
  float f;

  T= millis();
// Blink LED at rate proportional to fan speed. LED stays off
// when the engine is off.
  if (T >= blinkT) {
    digitalWrite (LED, (blinker ^= 1) && state != 0);
    blinkT= T + blinkrate;
  }
// Basic controller reaction time.
  if (T < nextT) return;
  nextT= T + ITER;

  gettemp();                          // read the sensor,
  switch (state) {

 // ENGINE OFF.
  case 0:
    info ("OFF");
    fangoal= 0;                       // stop fan (eventually)
    if (! engineoff) state= 1;        // watch for engine on
    break;

// ENGINE RUNNING. Calc fan speed goal.
  case 1:
    if (engineoff) {
      state= 2;                       // engine off, go cooldown
      break;
    }
    // From set point to RANGE degrees over set point, fan speed varies
    // SLOW to FAST.
    f= (temp - setpt) / (float)RANGE; // fraction 0 - 1  within RANGE
    if (f < 0.0) f= 0.0;              // bound it
    if (f > 1.0) f= 1.0;
    fangoal= f * (FAST - SLOW) + SLOW;

    // No sense running the fan when the engine is cold.
    if (temp <= setpt - COLDOFF) fangoal= 0;

    // If we are going to run the fan, then set the
    // minimum speed; below the themotor sits and hums.
    if ((fanspeed < SLOW) && (fangoal >= SLOW))
      fanspeed= SLOW;                // minimum motor speed
      
    info ("Run");
    break;

// JUST TURNED OFF.
  case 2:
    if (fangoal > HALF)       fangoal= HALF;
    else if (fangoal > SLOW)  fangoal= SLOW; // but not SLOW
    else                      fangoal= OFF; // limit cooldown speed
    info ("Engine off");
    COOLTIMER= T; COOLTIMER += COOLTIME;
    state= 3;
    break;
 
// RUN THE COOLDOWN CYCLE.
  case 3:
    info ("Cool down");
    if (! engineoff) state= 1;        // car turned back on
    if (T > COOLTIMER) state= 0;      // else wait out timer
    break;
  }

// Here we adjust the actual fan speed, slowly, to match the
// fan speed goal.

  d= fangoal - fanspeed;              // speed difference
  if (d < -FROC) d= -FROC;
  if (d > FROC) d= FROC;              // limit rate of change
  fanspeed += d;
  blinkrate= (255 - fanspeed) * 5; // adjust LED blink rate
  if (blinkrate < 35) blinkrate= 35;
  if (blinkrate > 1200) blinkrate= 1200;
  analogWrite (FAN, fanspeed);  
}


/* Display useful junk. */

void info (char *m) {
    Serial.print (m);
    Serial.print (" setpt="); Serial.print(setpt, DEC);
    Serial.print (" temp="); Serial.print(temp, DEC);
    Serial.print (" speed="); Serial.print(fanspeed, DEC);
    Serial.print (" goal="); Serial.print(fangoal, DEC);
    Serial.println (engineoff ? " (Off)" : " (On)");
}




/*
Read the temperature sensor and return the reading, or -1
if the car is off. This calculates an average to filter out
spurious or momentary readings.
*/

void gettemp (void) {

int i;
unsigned long sum;

  // Calc set point as degrees F.
  setpt= analogRead(CAL) * (float)((SETMAX - SETMIN) / 1024.0) + SETMIN;

  avg[a]= ((float)analogRead(SENS) * VPB) * SENSSLOPE + SENSINTERCEPT;

  // If a valid temperature, add to average temp.
  if (avg[a] < FS) {
    if (++a == AVG) a= 0;
    if (++oncount > LOWPASS) {           // if we have enough samples
      oncount= LOWPASS;
      offcount= 0;                       // car is not off
      engineoff= 0;

      for (i= 0, sum= 0L; i < AVG; i++)  // create average temp
        sum += avg[i];
      temp= sum / AVG;
    }
  // else engine is off.
  } else if (++offcount >= LOWPASS) {
    oncount= 0;                          // require refill of avg buff
    offcount= LOWPASS;                   // (might be off a long time)
    engineoff= 1;                        // (don't wrap around)
  }
}