Hello good people. I am in the upstart of making a fan control station for a battery backup space in a storage room. It shall, if I get something finished, take a few temp readings here and there and control fans to act on the temp readings. Amongst other things there is a charger and a PSU that needs active cooling. I think I will end up with around five individual fans. I also want to be able to send some data from this in some way to be able to monitor it remotely. But that is a later subject. I have first started to think about how I will read that the fans are working. This was not so straight forward as I thought when I started. Most examples I found was reading revolutions over long periods with interrupts. This did not feel so applealing to me since I am not after any exact science around the revolutions and I did not want to weigh down the controller with long interrupts.
That made me start by instead reading the lengths of the hall sensor pulses. After some experimentation I think I have something working. I am very happy for comments both on the chosen method and on the code.
Here is the code for starters.
/*
This code reads the length of the DC fans built in hall sensor(s) activity.
The readings are in microseconds and with a formula this can be converted
and presented as Revolutions Per Minute (RPM) and/or percentage spinning
of the fans max revolutions per time unit.
*/
int hallSensorPin = 2; // Pin 2 is used to read the Hall sensors activity
unsigned long pulseTime; // Variable for storing the duration of a pulse.
// Since it is in microseconds it needs to be able
// to store a large value.
int revsPerMinute; // Variable for storing RPM value
int smoothedRevsPerMinute; // Variable for storing the smoothed RPM value
int percentage; // Variable for storing percentage value
int samples = 4; // Amount of samples that will be stored and used
// for smothing
/*
Set the pin that shall read the Hall Sensor to pull up its input to 5 V
with the built in 20k resistor. I have seen some lifting to fan 12 V line but that is
a much dirtyer voltage then the internal regulated 5 V line. Holding it internal also
makes the electrical loop much smaller and less sensitive to interference.
The hall sensors activates a sinking transistor that takes the voltage to LOW when
the hall sensors are active, ie it shorts its input to ground.
And start serial for test purpouse read outs to serial monitor.
*/
void setup() {
pinMode(hallSensorPin, INPUT_PULLUP);
Serial.begin(9600);
}
/*
The pulseIn function reads a pulse, either the high part or the low part. My fan makes two
pulses per revolution, ie high-low-high-low so first I read the high pulse, then I read the
low pulse. Then in the calculation to revs per minute I sum the high and low pulse and after
that multiplies by factor 2. This shall be a full revlution that we time.
The pulseIn way of measuring a fans revolutions per time unit is not very exact. A more
exact way would be to count the pulses over a longer time period. This is on the otther hand
taking up a longer processing time of maybe a second to get good readings. I want t avoid
that so I read the pulses instead which only takes a couple of milliseconds.
1200 RPM is 20 RPS which meens a full revolution takes 50 ms. I measure half a revolution.
The downside with this is that a fan is not very exact when you come down to microseconds
and the hall sensor readings can once in a while bug out and give a strange value.
I am on the other hand not interested in weather the fan spins with 1207 or or 1211 revs per
minute, I am interested to see if they do the work they are set to do so I dont care about
the exact value. On the other hand I do prefer a value that doesnt jump one step every time
it renews itself so therefore I am smoothing the output values so every new reading adds
the three readings before it and divides the total four readings with four to present a
smoothed average. This makes it less jumpy on the last digit.
*/
void loop() {
pulseTime = pulseIn(hallSensorPin, HIGH); // Read for how long the pulse is high
pulseTime += pulseIn(hallSensorPin, LOW); // Read for how long the pulse is low
revsPerMinute = (1000000 * 60)/(pulseTime * 2); // Convert pulse time to RPM
revsPerMinute = constrain(revsPerMinute, 0, 1560); // Dont let a buggy value get it to way out
// Smoothing out the value by adding earlyer values and divide to an average
smoothedRevsPerMinute = smoothedRevsPerMinute + ((revsPerMinute - smoothedRevsPerMinute)/samples);
// Calculate the fans spinning in percentage of its max which is approximately 1560 RPM
percentage = (smoothedRevsPerMinute / 15.6);
// Do some serial printing for checking the result for now
Serial.print("Halfrev Microseconds");
Serial.print("\t");
Serial.println(pulseTime, DEC);
Serial.print("Revolutions per minute");
Serial.print("\t");
Serial.println(smoothedRevsPerMinute, DEC);
Serial.print("Percentage of max revs");
Serial.print("\t");
Serial.println(percentage, DEC);
delay(5000);
}
And here is a results from a serial print:
Halfrev Microseconds 23385
Revolutions per minute 1303
Percentage of max revs 83
Halfrev Microseconds 23090
Revolutions per minute 1302
Percentage of max revs 83
Halfrev Microseconds 23090
Revolutions per minute 1302
Percentage of max revs 83
Halfrev Microseconds 23024
Revolutions per minute 1302
Percentage of max revs 83
Halfrev Microseconds 23024
Revolutions per minute 1302
Percentage of max revs 83
Halfrev Microseconds 23388
Revolutions per minute 1297
Percentage of max revs 83
Halfrev Microseconds 23130
Revolutions per minute 1297
Percentage of max revs 83
Halfrev Microseconds 22956
Revolutions per minute 1299
Percentage of max revs 83
Halfrev Microseconds 23222
Revolutions per minute 1297
Percentage of max revs 83
Halfrev Microseconds 23321
Revolutions per minute 1295
Percentage of max revs 83
Today is a rainy day so it was time for some foul programming. I was unhappy with that if I stopped the fan manually the code started to say that the revs are increasing. That is ofc bec the pulseIn-command reads the pulse lengths and they usually get shorter when the revs are increasing, but with one exception: it returns zero pulselength if it doesnt find a pulse. And zero is short.
I just couldnt make things work with If/ElseIf/Else or While commands so I ended up doing Gotos. Is this considered foul programming, could it be done neater? I shall say the code works. It does alarm when I stop the fan and it goes back to normal if I release it.
/*
* This code reads the length of the DC fans built in hall sensor(s) activity.
* The readings are in microseconds and with a formula this can be converted
* and presented as Revolutions Per Minute (RPM) and/or percentage spinning
* of the fans max revolutions per time unit.
*/
int hallSensorPin = 2; // Pin 2 is used to read the Hall sensors activity
unsigned long previousPulseTime; // Variable for storing the duration of a pulse.
word highPulseTime; // It stores the previous value to have something
word lowPulseTime; // compare a new value with
unsigned long newPulseTime; // Since it is in microseconds it needs to be able
// to store a large value.
int revsPerMinute; // Variable for storing RPM value
int smoothedRevsPerMinute; // Variable for storing the smoothed RPM value
int percentage; // Variable for storing percentage value
int samples = 3; // Amount of samples that will be stored and used
// for smoothing
int ledPin = 13; // Pin 13 is used to test alarm
/*
* Set the pin that shall read the Hall Sensor to pull up its input to 5 V
* with the built in 20k resistor. I have seen some lifting to fan 12 V line but that is
* a much dirtyer voltage then the internal regulated 5 V line. Holding it internal also
* makes the electrical loop much smaller and less sensitive to interference.
* The hall sensors activates a sinking transistor that takes the voltage to LOW when
* the hall sensors are active, ie it shorts its input to ground.
* And start serial for test purpouse read outs to serial monitor.
*/
void setup() {
pinMode(ledPin, OUTPUT); // Set LedPin to output
pinMode(hallSensorPin, INPUT_PULLUP); // Pull up the sensor pin to 5V
highPulseTime = pulseIn(hallSensorPin, HIGH); // Read once for how long the pulse is high
lowPulseTime = pulseIn(hallSensorPin, LOW); // Read once for how long the pulse is low
newPulseTime = highPulseTime + lowPulseTime; // Create a previous value by telling it to
previousPulseTime = newPulseTime; // be the same as the new value we just read.
// to get a starter position
Serial.begin(9600); // We start a serial for debugging readouts
}
/*
* The pulseIn function reads a pulse, either the high part or the low part. My fan makes two
* pulses per revolution, ie high-low-high-low so first I read the high pulse, then I read the
* low pulse. Then in the calculation to revs per minute I sum the high and low pulse and after
* that multiplies by factor 2. This shall be a full revlution that we time.
*
* The pulseIn way of measuring a fans revolutions per time unit is not very exact. A more
* exact way would be to count the pulses over a longer time period. This is on the other hand
* taking up a longer processing time of maybe a second to get good readings. I want to avoid
* that so I read the pulses instead which only takes a couple of milliseconds.
*
* 1200 RPM is 20 RPS which meens a full revolution takes 50 ms. I measure half a revolution.
* The downside with this is that a fan is not very exact when you come down to microseconds
* and the hall sensor readings can once in a while bug out and give a strange value.
* I am on the other hand not interested in weather the fan spins with 1207 or or 1211 revs per
* minute, I am interested to see if they do the work they are set to do so I dont care about
* the exact value. On the other hand I do prefer a value that doesnt jump one step every time
* it renews itself so therefore I am smoothing the output values so every new reading adds
* the three readings before it and divides the total four readings with four to present a
* smoothed average. This makes it less jumpy on the last digit.
*
* I also added an outlier corrector which is described below.
*/
void loop() {
run: // goto tag, see below
digitalWrite(13, LOW);
highPulseTime = pulseIn(hallSensorPin, HIGH); // Read for how long the pulse is high
lowPulseTime = pulseIn(hallSensorPin, LOW); // Read for how long the pulse is low
newPulseTime = highPulseTime + lowPulseTime;
/*
* Here I tried with alot of If, Else If and Else commands but it would not work for me.
* If I tried If => Pulse Time = 0 and Else If if it was not 0, somehow the smoothing
* algorithms below bugged out so it just told me that the revs are going up all the way
* to 100% while tha fan was actually stopped and the Led Pin did not want to shine. I
* guess the interpretation of If => Else If and then new If commands does not work.
* I also tried While for the alarm if the fan stoppes and it worked so far but when I
* released the fan it was stuck in the While-loop.
*/
if (newPulseTime == 0) { // A goto command. If statement is true
goto alarm; // we jump down to alarm: in the end
}
if (newPulseTime < (previousPulseTime * 0.9)) { //Kind of a high-pass filter
(newPulseTime = (previousPulseTime * 0.9)); //Takes outlier values down
}
if (newPulseTime > (previousPulseTime * 1.1)) { //Kind of a high-pass filter
(newPulseTime = (previousPulseTime * 1.1)); //Takes outlier values down
}
revsPerMinute = (1000000 * 60)/(newPulseTime * 2); // Convert pulse time to RPM
// Smoothing out the value by adding earlyer values and divide to an average
smoothedRevsPerMinute = smoothedRevsPerMinute + ((revsPerMinute - smoothedRevsPerMinute)/samples);
// Calculate the fans spinning in percentage of its max which is approximately 1560 RPM
percentage = (smoothedRevsPerMinute / 15.55); //The fans max revs are about 1550
percentage = constrain(percentage, 0, 100); //Constrains percentage scale
// Do some serial printing for checking the result for now
Serial.print("High Pulsetime");
Serial.print("\t");
Serial.print("\t");
Serial.println(highPulseTime, DEC);
Serial.print("Low Pulsetime");
Serial.print("\t");
Serial.print("\t");
Serial.println(lowPulseTime, DEC);
Serial.print("Halfrev Microseconds");
Serial.print("\t");
Serial.println(newPulseTime, DEC);
Serial.print("Revolutions per minute");
Serial.print("\t");
Serial.println(smoothedRevsPerMinute, DEC);
Serial.print("Percentage of max revs");
Serial.print("\t");
Serial.println(percentage, DEC);
previousPulseTime = newPulseTime;
alarm:
if (newPulseTime > 0) { //If the pulse is not zero wait a while and go to run
delay(5000);
goto run;
}
else { //otherwise activate alarm, wait a bit and check again
digitalWrite(ledPin, HIGH);
}
delay(5000);
}
I didnt like goto so I went to function blocks with millis instead. Much better for the coming functions. Now the usual (non alarm) functions ticks on even if the alarm triggers.
/*
* This code reads the length of the DC fans built in hall sensor(s) activity.
* The readings are in microseconds and with a formula this can be converted
* and presented as Revolutions Per Minute (RPM) and/or percentage spinning
* of the fans max revolutions per time unit.
*/
const int hallSensorPin = 2; // Pin 2 is used to read the Hall sensors activity
const int readFanInterval = 5000; // number of millisecs between reading the fan
const int alarmInterval = 1000; // number of millisecs for the alarm to go
unsigned long previousPulseTime; // Variable for storing the duration of a pulse.
word highPulseTime; // It stores the previous value to have something
word lowPulseTime; // compare a new value with
unsigned long newPulseTime; // Since it is in microseconds it needs to be able
// to store a large value.
unsigned long currentMillis = 0; // stores the value of millis() in each iteration of loop()
unsigned long previousReadFan = 0;// will store last time the LED was updated
unsigned long previousAlarm = 0; // will store last time the LED was updated
int revsPerMinute; // Variable for storing RPM value
int smoothedRevsPerMinute; // Variable for storing the smoothed RPM value
int percentage; // Variable for storing percentage value
const int samples = 3; // Amount of samples that will be stored and used
// for smoothing
const int ledPin = 13; // Pin 13 is used to test alarm
/*
* Set the pin that shall read the Hall Sensor to pull up its input to 5 V
* with the built in 20k resistor. I have seen some lifting to fan 12 V line but that is
* a much dirtyer voltage then the internal regulated 5 V line. Holding it internal also
* makes the electrical loop much smaller and less sensitive to interference.
* The hall sensors activates a sinking transistor that takes the voltage to LOW when
* the hall sensors are active, ie it shorts its input to ground.
* And start serial for test purpouse read outs to serial monitor.
*/
void setup() {
currentMillis = millis(); // capture the latest value of millis()
pinMode(ledPin, OUTPUT); // Set LedPin to output
pinMode(hallSensorPin, INPUT_PULLUP); // Pull up the sensor pin to 5V
highPulseTime = pulseIn(hallSensorPin, HIGH); // Read once for how long the pulse is high
lowPulseTime = pulseIn(hallSensorPin, LOW); // Read once for how long the pulse is low
newPulseTime = highPulseTime + lowPulseTime; // Create a previous value by telling it to
previousPulseTime = newPulseTime; // be the same as the new value we just read.
// to get a starter position
Serial.begin(9600); // We start a serial for debugging readouts
}
/*
* The pulseIn function reads a pulse, either the high part or the low part. My fan makes two
* pulses per revolution, ie high-low-high-low so first I read the high pulse, then I read the
* low pulse. Then in the calculation to revs per minute I sum the high and low pulse and after
* that multiplies by factor 2. This shall be a full revlution that we time.
*
* The pulseIn way of measuring a fans revolutions per time unit is not very exact. A more
* exact way would be to count the pulses over a longer time period. This is on the other hand
* taking up a longer processing time of maybe a second to get good readings. I want to avoid
* that so I read the pulses instead which only takes a couple of milliseconds.
*
* 1200 RPM is 20 RPS which meens a full revolution takes 50 ms. I measure half a revolution.
* The downside with this is that a fan is not very exact when you come down to microseconds
* and the hall sensor readings can once in a while bug out and give a strange value.
* I am on the other hand not interested in weather the fan spins with 1207 or or 1211 revs per
* minute, I am interested to see if they do the work they are set to do so I dont care about
* the exact value. On the other hand I do prefer a value that doesnt jump one step every time
* it renews itself so therefore I am smoothing the output values so every new reading adds
* the three readings before it and divides the total four readings with four to present a
* smoothed average. This makes it less jumpy on the last digit.
*
* I also added an outlier corrector which is described below.
*/
void loop() {
read_fan_rpm();
}
void read_fan_rpm() { // Separate function block for reading fan
if (millis() - previousReadFan >= readFanInterval) {
highPulseTime = pulseIn(hallSensorPin, HIGH); // Read for how long the pulse is high
lowPulseTime = pulseIn(hallSensorPin, LOW); // Read for how long the pulse is low
newPulseTime = highPulseTime + lowPulseTime;
if (newPulseTime == 0) { // If both high and low pulsetime
fan_alarm(); // returns zero call function alarm
}
digitalWrite(13, LOW);
if (newPulseTime < (previousPulseTime * 0.9)) { //Kind of a high-pass filter
(newPulseTime = (previousPulseTime * 0.9)); //Takes outlier values down
}
if (newPulseTime > (previousPulseTime * 1.1)) { //Kind of a high-pass filter
(newPulseTime = (previousPulseTime * 1.1)); //Takes outlier values down
}
revsPerMinute = (1000000 * 60)/(newPulseTime * 2); // Convert pulse time to RPM
// Smoothing out the value by adding earlyer values and divide to an average
smoothedRevsPerMinute = smoothedRevsPerMinute + ((revsPerMinute - smoothedRevsPerMinute)/samples);
// Calculate the fans spinning in percentage of its max which is approximately 1560 RPM
percentage = (smoothedRevsPerMinute / 15.55); //The fans max revs are about 1550
percentage = constrain(percentage, 0, 100); //Constrains percentage scale
// Do some serial printing for debugging
Serial.print("High Pulsetime");
Serial.print("\t");
Serial.print("\t");
Serial.println(highPulseTime, DEC);
Serial.print("Low Pulsetime");
Serial.print("\t");
Serial.print("\t");
Serial.println(lowPulseTime, DEC);
Serial.print("Halfrev Microseconds");
Serial.print("\t");
Serial.println(newPulseTime, DEC);
Serial.print("Unsmoothed per minute");
Serial.print("\t");
Serial.println(revsPerMinute, DEC);
Serial.print("Revolutions per minute");
Serial.print("\t");
Serial.println(smoothedRevsPerMinute, DEC);
Serial.print("Percentage of max revs");
Serial.print("\t");
Serial.println(percentage, DEC);
previousPulseTime = newPulseTime;
previousReadFan += readFanInterval;
}
}
void fan_alarm() { // Separate function block for alarm
if (millis() - previousAlarm >= alarmInterval) {
digitalWrite(ledPin, HIGH); // Activates the LedPin
Serial.println("Fan has stopped!"); // Prints a warning message
previousAlarm += alarmInterval;
}
}