Temperature controlled fan

rpm

For quite a while I had a Spark Core laying around. I never really made anything with it since I found Electric Imp’s more suited or most things I need.

Today I just needed something to adjust the PWM duty cycle to a fan, depending on the temperature, and since I already had a Spark Core laying around, with a TMP36 temperature sensor attached to it, that ended up as the victim for this project.

Because the Spark Core also got WiFi build in, I decided to make it show the different variables it use to control the fan, which would be temperature, pwm and just for fun RPM.

The code below is what runs on my Spark Core, with the pwm wire connected to A1 on the Core, and the rpm wire connected to A0. Connect a 10k resistor between A0 and 3v3 to get a stable rpm count.

The readings from the API can be used on a page like Freeboard.io, like I did here https://freeboard.io/board/538632b0f1776c1c2e00058d. One problem with the Spark Core is how the api works, where you can only show one result, so you have to make multiple calls, unless you nest them in one string like I did in my code.

When reading the result from the API it will show rpm|pwm|temperature, which right now is “result”: “3150|67|25.83”, to use that on freeboard we will have to split that up before we can use it. To split it up, I used this code

var str = datasources["sparkcore"].result;
var res = str.split("|");
return res[0];

This will return RPM from the string, and res[1] would return PWM.

And to get all the magic to happen, you can use this code:

/***********************************************************************************************************
Variables used for setting fan pins
***********************************************************************************************************/
#define fanPinRPM               A0      // Pin connected to the fan hall sensor
#define fanPinPWM               A1      // Pin connected to the fan PWM input

/***********************************************************************************************************
Variables used for tuning pwm duty cycle
***********************************************************************************************************/
#define tempMin                 25      // Temperature where PWM will be equal to pwmMin
#define tempMax                 40      // Temperature where PWM will be equal to pwmMax
#define pwmMin                  5       // Minimum PWM duty cycle (min 0)
#define pwmMax                  255     // Maximum PWM duty cycle (max 255)
#define pwmCalcDelay            100     // Time in milliseconds between calculating and setting PWM
#define pwmMultiplier           100     // Multiplying values to increase resolution on the map function

/***********************************************************************************************************
Variables used for reading the TMP36 temperature sensor
The temperature can be accessed by opening https://api.spark.io/v1/devices/{device id}/status?access_token={access token}
***********************************************************************************************************/
#define TMP36pin                A7      // Pin which the sensor is connected to
#define readTMP36trot           100     // Time in milliseconds between temperature readings

/***********************************************************************************************************
Variables used for averaging temperature readings
***********************************************************************************************************/
#define tempArraySize           10      // Size of the arry holding values for averaging
float   temps[tempArraySize];           // Arry holding temperature readings
float   temperature =           0;      // Current average temperature
int     idx =                   0;      // Index of where we are in the array

/***********************************************************************************************************
Variables used for averaging temperature readings
***********************************************************************************************************/
#define rpmCalcDelay            2000    // Milliseconds between calculating fan RPM, increase to get a slower update time, but more precise number
int fanPWM = 0;                         // Holding PWM duty cycle
int fanRPM = 0;                         // Holding calculated fan RPM

/***********************************************************************************************************
Variable used to hold api variable reply
***********************************************************************************************************/
#define statusStringDelay       1000    // Time in milliseconds between setting the status string
char statusString[16];                   // Holding the result in the json api reply

unsigned long millisNow = 0;

void setup()
{
    Spark.variable("status", &statusString, STRING); // Register the status variable in the spark api

    attachInterrupt(fanPinRPM, pulseCount, FALLING); // Set interrupt to count pulses from the fan
    pinMode(fanPinPWM, OUTPUT); // Set pin to output so we can do pwm with it
}

void loop()
{
    millisNow = millis();   // Setting millis() once so we dont have to calculate it for each function

    rpmCalc();              // Calculate fan RPM
    readTMP36();            // Read from the temperature sensor and set the temperature variable
    pwmCalc();              // Calculate PWM and adjust fan speed
    statusStringSet();      // Build and set the status string
}

/***********************************************************************************************************
Build and set status string for the API result
***********************************************************************************************************/
unsigned long statusStringSetLast = 0;
void statusStringSet()
{
    if (millisNow - statusStringSetLast < statusStringDelay) return; // Return to loop if time since last run is too short
    statusStringSetLast = millisNow;

    snprintf(statusString, sizeof statusString, "%d|%d|%4.2f", fanRPM, fanPWM, temperature); // rpm|pwm|temperature
}

/***********************************************************************************************************
Count fan pulses and calculate fan RPM
***********************************************************************************************************/
unsigned long fanPulses = 0;
void pulseCount()
{
    fanPulses++; // Add one pulse to the counter
}

unsigned long fanRPMreset = 0;
void rpmCalc()
{
    if (millisNow - fanRPMreset < rpmCalcDelay) return; // Return to loop if time since last run is too short

    noInterrupts(); // Disable interrupts while we calculate RPM

    int mS = millisNow - fanRPMreset; // Calculate time since last time the counter was reset

    fanRPM = (fanPulses * (60000/mS)) / 2; // Calculate the rpm and divide by two because the fan gives two pules for one revolution
    fanPulses = 0; // Set pulses to zero after calculating RPM

    fanRPMreset = millisNow; // Store when the counter was reset last

    interrupts(); // Enable interrupts and start counting again
}

/***********************************************************************************************************
Count fan pulses and calculate fan RPM
***********************************************************************************************************/
unsigned long pwmCalcLast = 0;
void pwmCalc()
{
 if (millisNow - pwmCalcLast < pwmCalcDelay) return; // Return to loop if time since last run is too short
 pwmCalcLast = millisNow;

 fanPWM = map(temperature*pwmMultiplier, tempMin*pwmMultiplier, tempMax*pwmMultiplier, pwmMin*pwmMultiplier, pwmMax*pwmMultiplier)/pwmMultiplier; // Calculate PWM duty cycle with multiplier for better resolution
 fanPWM = constrain(fanPWM, pwmMin, pwmMax); // Make sure PWM stays within the set limits

 analogWrite(fanPinPWM, fanPWM); // Set the pin to the calculated duty cycle
}

/***********************************************************************************************************
Read from the TMP36 sensor, calculate themperature and average tmperature
***********************************************************************************************************/
unsigned long readTMP36last = 0; // Holding time of when temperature was last checked
void readTMP36() // Function for reading from the sensor
{
 if (millisNow - readTMP36last < readTMP36trot) return; // Return to loop if time since last run is too short

 int analogReading = analogRead(TMP36pin);

 if (readTMP36last == 0) // If this is the first time, we are going to fill the array with readings so it does not return a wrong low temperature
 {
 for(int i = 0; i < tempArraySize; i++) // Run through the array holding the temperatures
 {
 temps[i] = ((((analogReading*3.3)/4095.0)-0.5)*100.0); // Put reading into the array
 }
 }

 readTMP36last = millisNow; // More than 500ms (default) has passed, store the time we are reading at so we know when to do it again

 temps[idx] = ((((analogReading*3.3)/4095.0)-0.5)*100.0); // Check analog input and calculate the temperature

 idx += 1;

 if (idx >= tempArraySize) idx = 0; // Add one to the array index, and check that we dont go above its size, if we do, set index to 0

 double total = 0; // Used to hold total value of the array
 for(int i = 0; i < tempArraySize; i++) // Run through the array holding the temperatures
 {
 total += temps[i]; // Add each temperature in the array to the total
 }

 temperature = total / tempArraySize; // Set temperature variable to total temperature, divided by the array size, so we get an average temperature
}

To verify the core is picking up the pulses correctly, and verify the calculation is correct, I used my Extech EX330 to measure the frequency on the RPM wire from the fan, and then manually calculate the RPM from that.

2015-01-31 22.27.40 (Large)

As you can see on the picture, my multimeter says 82.48 Hz, and the core has reported the fan to be spinning at 2460 rpm. To verify this I simply multiplied 82.48 with 30 (30 because the fan gives two pulses for each rotation, so instead of dividing the result by two, I only multiply with 30 seconds instead of 60 seconds for a minute), which is 2474.4, but that isn’t what the core calculated it to be? The reason for this difference is the time I set the core to count pulses from the fan, in this case 1000 ms. The number is then rounded down, so if we instead do 82 * 30 we get 2460, which matches the calculation by the core perfectly.

To get a more accurate reading, you have to set the rpmCalcDelay to something higher. I ended up changing mine from 1000 ms to 2000 ms.

And some old pictures from when I first made the arduino based controller.

adabter_8 adabter_9 adabter_10 adabter_11 adabter_12 adabter_13 adabter_14 adabter_15 adabter_17

Leave a Reply