# 4-pin PC Fan PWM control and speed monitoring

I want to control and monitor the speed of a 4-pin PC fan using an esp8266.

When there is no PWM control signal sent to the fan from the esp this code reports the fan speed correctly. However, when the esp PWM control signal is connected to the fan things go wrong with the speed reporting, but the fan speed actually responds as expected to the PWM signal.

The setup:

• The fan is driven from a 12V supply and the NodeMCU v2 esp8266 is powered by USB from a PC. The 12V supply and the esp8266 have a common ground through the esp’s GND pin; and
• The tach signal from the fan is pulled up the 3.3V pin of the esp and is connected to pin D3 of the esp.

When the system is connected in this state (no PWM signal) the fan speed is at maximum and its speed is correctly reported in the serial monitor at approximately 1410 RPM. I have used the debugging tools in VisualMicro to watch the vaue of the interruptCounter variable in the computFanSpeed() function during operation and its value gets to 49 before it is reset to zero after exiting loop().

The PWM signal originates from esp pin D1 and is measured to be 2.0V using a multimeter. When this PWM signal is connected to the PWM input of the fan it responds as expected, however the speed reported in the serial monitor climbs to a non-sensible number (123300 RPM). The value of interruptCounter variable in the computFanSpeed() function gets to 4180 before it is reset to zero after exiting loop().

I don’t understand where things are going wrong – specifically why is the handleIinterrupt() function being triggered when the fan is operating under PWM control by the esp?

Another note: For some reason if any value other than 0 is provided to the pin argument in the attachInterrupt('pin', 'ISR', 'mode') function the fan speed recorded in the serial monitor is 0 RPM. I have tried a several combinations of different pins and values for 'pin' and have not been able to have the speed reported in the serial monitor - it always shows 0 RPM.

volatile int interruptCounter; //counter use to detect hall sensor in fan
int RPM;                      //variable used to store computed value of fan RPM
unsigned long previousmills;

//NODEMCU ESP8266
#define tachInputPIN  4
#define pwmOutputPin 5
#define sensorInterrupt 0
#define pwmDuty 630

#define calculationPeriod 1000 //Number of milliseconds over which to interrupts

void setup()
{
Serial.begin(115200);

previousmills = 0;
interruptCounter = 0;
RPM = 0;

//NODEMCU esp8266
analogWriteFreq(8000);
pinMode(pwmOutputPin, OUTPUT);

attachInterrupt(sensorInterrupt, handleInterrupt, RISING);
pinMode(tachInputPIN, INPUT);
}

void loop()
{

// NODEMCU esp8266
analogWrite(pwmOutputPin, pwmDuty);

if((millis() - previousmills) > calculationPeriod) {// Process counters once every second
detachInterrupt(sensorInterrupt);
computeFanSpeed();
displayFanSpeed();
previousmills = millis();
interruptCounter = 0;
attachInterrupt(sensorInterrupt, handleInterrupt, RISING);
}

}

void ICACHE_RAM_ATTR handleInterrupt() { //This is the function called by the interrupt
interruptCounter++;
}

void computeFanSpeed() {
//interruptCounter counts 2 pulses per revolution of the fan over a one second period
RPM = (0.5) * (interruptCounter / (calculationPeriod/1000)) * 60;
}

void displayFanSpeed() {
Serial.print(RPM, DEC);        //Prints the computed fan speed to the serial monitor
Serial.print(" RPM\r\n");      //Prints " RPM" and a new line to the serial monitor
}

• The ESP8266 PWM is itself interrupt driven, it is probably interfering with your rpm sensor interrupts. Read this: lurchi.wordpress.com/2016/06/29/… – Jack Creasey Jan 5 at 18:31
• You are probably right, I thought of that. But why would it only happen when the PWM signal is connected to the fan? When it’s open everything seems to function correctly- in both cases the esp is generating the PWM signal. – D-dawg Jan 6 at 16:13
• This is written for 3 pin fans. But could apply to a 4 pin fans as well. baldengineer.com/pwm-3-pin-pc-fan-arduino.html If it was me, I would try adding a capacitor to the pwm pin and a small capacitance to the input pin to see if it makes a difference. I would also look at reading the falling edge instead of the rising edge. – George Feb 14 at 0:14
• Just did the same with the following results: 1) isr attached to FALLING 2) inside isr I've added additional check if(digitalRead(sensorInput)==0) counter++; 3) pinMode(sensortInput, INPUT_PULLUP); In this case everything is fine. Please make sure you have connected the common ground - uC ground shall be common with fan ground plane. – BbIKTOP May 16 at 8:00
• Also, there's a problem that most fans cannot be completely stopped using pwm control pin. That's why it's better to use 3pin schematic. But in this case, most fans could drain power from the sensor and control pins. Also, sensor could not work correctly because it takes a power from the main power line, which is pwm in 3pins case – BbIKTOP May 16 at 8:02

The esp seems to cache its interrupts or something weird (related to disabling and enabling interrupts)

try this:

volatile int interruptCounter; //counter use to detect hall sensor in fan
int RPM;                      //variable used to store computed value of fan RPM
unsigned long previousmills;

//NODEMCU ESP8266
#define tachInputPIN  D5
#define pwmOutputPin D6

#define pwmDuty 1024

#define calculationPeriod 1000 //Number of milliseconds over which to interrupts

void ICACHE_RAM_ATTR handleInterrupt() { //This is the function called by the interrupt
interruptCounter++;

}

void setup()
{
Serial.begin(115200);

previousmills = 0;
interruptCounter = 0;
RPM = 0;

//NODEMCU esp8266

pinMode(pwmOutputPin, OUTPUT);
pinMode(tachInputPIN, INPUT_PULLUP);
analogWriteFreq(25000);
analogWrite(pwmOutputPin, pwmDuty);
attachInterrupt(digitalPinToInterrupt(tachInputPIN), handleInterrupt, FALLING);

}

void loop()
{

// NODEMCU esp8266
//  analogWrite(pwmOutputPin, pwmDuty);

if((millis() - previousmills) > calculationPeriod) {// Process counters once every second

previousmills = millis();
int count = interruptCounter;
interruptCounter = 0;
computeFanSpeed(count);
displayFanSpeed();

}
yield();
}

void computeFanSpeed(int count) {
//interruptCounter counts 2 pulses per revolution of the fan over a one second period
RPM =  count /2 * 60 ;
}

void displayFanSpeed() {
Serial.print(RPM, DEC);        //Prints the computed fan speed to the serial monitor
Serial.print(" RPM\r\n");      //Prints " RPM" and a new line to the serial monitor
}


`