Reading Fan RPM when Fan is PWM Controlled

I have a circuit which controls the fan speed based on the amount of light in the room. The fan I'm using is a 3pin fan, the yellow wire is the reporting one. My code is able to read the RPM of the fan when I set the PWM pin to either 0 or 255. Ex:

analogWrite(transistor,0); //RPM 0
analogWrite(transistor,255); // RPM around 900

however, say I were to write

analogWrite(transistor,123); //roughly half the speed

the RPM would not be accurate, and would be very far off.

Here's the code I'm currently using:

/*To disable interrupts:
 cli();                // disable global interrupts

and to enable them:  
 sei();                // enable interrupts
*/


                                   //Varibles used for calculations
int NbTopsFan; 
int Calc;

int ldr; //Light sensor

//Pin Locations

int OnGreenLed = 4;

int OffRedLed = 7;

int SpeedOrangeLed = 11;

int buttonPin = 8; //For Button

int transistor = 9;

int currentState = LOW;

int hallsensor = 2;



 //int ldr = 0;
 //  ldr = analogRead(0)/4;
                        
typedef struct{                  //Defines the structure for multiple fans and their dividers
  char fantype;
  unsigned int fandiv;
}fanspec;

//Definitions of the fans
fanspec fanspace[3]={{0,1},{1,2},{2,8}};

char fan = 1;   //This is the varible used to select the fan and it's divider, set 1 for unipole hall effect sensor 
               //and 2 for bipole hall effect sensor 


void rpm ()      //This is the function that the interupt calls 
{ 
 NbTopsFan++; 
} 

//This is the setup function where the serial port is initialised,and the interrupt is attached
void setup() 
{ 
  pinMode (hallsensor, INPUT);
  pinMode(OffRedLed,OUTPUT);  
  pinMode(OnGreenLed,OUTPUT);
  
  pinMode(SpeedOrangeLed,OUTPUT);
  
 pinMode(buttonPin, INPUT);  
 Serial.begin(9600); 
 attachInterrupt(0, rpm, RISING); 
 setPwmFrequency(9,1); //to remove the high frequency sound when fan is being pwm controlled
 
 
} 
void loop () 
{
  
  // read the state of the pushbutton value:
  ldr = analogRead(A1);
 currentState = digitalRead(buttonPin);

  
 if(currentState == HIGH){
   
  digitalWrite(OffRedLed, LOW);
  digitalWrite(OnGreenLed, HIGH);
  
  analogWrite(SpeedOrangeLed,ldr/4);
 // delay(10);
  analogWrite(transistor,ldr/4);
     } 
  
  else{
    // turn LED off:
    digitalWrite(OffRedLed, HIGH); 
    digitalWrite(OnGreenLed, LOW);
    
  digitalWrite(SpeedOrangeLed,LOW);
    analogWrite(transistor,0);
    
 
  }
   NbTopsFan = 0;	//Set NbTops to 0 ready for calculations
   sei();		//Enables interrupts
   delay (1000);	//Wait 1 second
   cli();		//Disable interrupts
   Calc = ((NbTopsFan * 60)/fanspace[fan].fandiv); //Times NbTopsFan (which is apprioxiamately the fequency the fan is spinning at) by 60 seconds before dividing by the fan's divider
   Serial.print (Calc, DEC); //Prints the number calculated above
  Serial.print (" rpm\r Button State: "); //Prints " rpm" and a new line
  Serial.print(currentState);
  Serial.print(" ldr reading = ");
Serial.print(ldr);
Serial.print("\n");

}


void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

is the tutorial I followed on how to read the RPM.

Any suggestions?

In order to read the fan speed via the tacho when you are controlling the fan speed, you need to do one of the following:

  1. Control the fan speed by linearly adjusting the voltage you supply to it, instead of using PWM; or

  2. Use a 4-pin fan. This has a pin for the +12V supply and a separate pin for the PWM control input. The permanent +12V supply allows the tacho to provide an output regardless of the PWM.

First off, do you have a pullup resistor installed on the TACH wire? What color are the rest of your fan wires as yellow is usually the +12V wire? Assuming all that is right, then the main problem is that the hall sensor that generates the TACH signal can only do so with the power on. Since you are PWMing the main power, it can't generate pulses when the switch is off. What is your PWM frequency?

dc42 just told you how to fix it, but I already typed all this. :slight_smile:

Thanks for the responses

dc42:
In order to read the fan speed via the tacho when you are controlling the fan speed, you need to do one of the following:

  1. Control the fan speed by linearly adjusting the voltage you supply to it, instead of using PWM; or

  2. Use a 4-pin fan. This has a pin for the +12V supply and a separate pin for the PWM control input. The permanent +12V supply allows the tacho to provide an output regardless of the PWM.

I would imagine both those options require me to buy something, so I'll probably take the loss on this one.

However, I did just search up some info about your first suggestion... it lead me here

I read something about Pulse Stretching...
Is this something you can implement with code?

afremont:
First off, do you have a pullup resistor installed on the TACH wire? What color are the rest of your fan wires as yellow is usually the +12V wire? Assuming all that is right, then the main problem is that the hall sensor that generates the TACH signal can only do so with the power on. Since you are PWMing the main power, it can't generate pulses when the switch is off. What is your PWM frequency?

dc42 just told you how to fix it, but I already typed all this. :slight_smile:

Yes I have the pull up resistor, the other wires are red and black (power and ground). PWM frequency I believe is 31250hz.

Haha, I figured it out. I kinda cheated though but it's working for my purposes.

Here's the finished product;

/*To disable interrupts:
 cli();                // disable global interrupts

and to enable them:  
 sei();                // enable interrupts
*/


                                   //Varibles used for calculations
int NbTopsFan; 
int Calc;
long interval1 = 5000; 
long time1 = millis();
int ldr; //Light sensor

//Pin Locations

int OnGreenLed = 4;

int OffRedLed = 7;

int SpeedOrangeLed = 11;

int buttonPin = 8; //For Button

int transistor = 9;

int currentState = LOW;

int hallsensor = 2;

int dutycycle = 0;

int previousdc=0;



 //int ldr = 0;
 //  ldr = analogRead(0)/4;
                        
typedef struct{                  //Defines the structure for multiple fans and their dividers
  char fantype;
  unsigned int fandiv;
}fanspec;

//Definitions of the fans
fanspec fanspace[3]={{0,1},{1,2},{2,8}};

char fan = 1;   //This is the varible used to select the fan and it's divider, set 1 for unipole hall effect sensor 
               //and 2 for bipole hall effect sensor 


void rpm ()      //This is the function that the interupt calls 
{ 
 NbTopsFan++; 
} 

//This is the setup function where the serial port is initialised,and the interrupt is attached
void setup() 
{ 
  pinMode (hallsensor, INPUT);
  pinMode(OffRedLed,OUTPUT);  
  pinMode(OnGreenLed,OUTPUT);
  pinMode(SpeedOrangeLed,OUTPUT);
   pinMode(buttonPin, INPUT);  
  analogWrite(transistor,dutycycle);
 Serial.begin(9600); 
 attachInterrupt(0, rpm, RISING); 
 setPwmFrequency(9,1);
 
 
} 
void loop () 
{
  
  // read the state of the pushbutton value:
  ldr = analogRead(A1);
 currentState = digitalRead(buttonPin);
 unsigned long m = millis();
  
 if(currentState == HIGH){
   if(ldr >= 800)
   {
     dutycycle = 255;
    analogWrite(transistor,dutycycle); //max power 
    
  digitalWrite(OffRedLed, LOW);
  digitalWrite(OnGreenLed, HIGH);
  analogWrite(SpeedOrangeLed,dutycycle);
  previousdc=dutycycle;
   }
   else{
   dutycycle = ldr/4;
  digitalWrite(OffRedLed, LOW);
  digitalWrite(OnGreenLed, HIGH);   
  analogWrite(transistor,dutycycle);
  analogWrite(SpeedOrangeLed,dutycycle);
previousdc=dutycycle;
     } 
 }
  else{
    // turn LED off:
    dutycycle = 0;
    digitalWrite(OffRedLed, HIGH); 
    digitalWrite(OnGreenLed, LOW);
    
  digitalWrite(SpeedOrangeLed,dutycycle);
    analogWrite(transistor,dutycycle);
 previousdc=dutycycle;
    
 
  }
  

  if (m - time1 > interval1){
    time1 = m;

    if (dutycycle == 255)
      dutycycle = 255;
    else
      dutycycle = previousdc;

    digitalWrite(transistor, dutycycle);  //********************************
  }
  
   NbTopsFan = 0;	//Set NbTops to 0 ready for calculations

   sei();		//Enables interrupts
   delay (1000);	//Wait 1 second
   cli();		//Disable interrupts
   Calc = ((NbTopsFan * 60)/fanspace[fan].fandiv); //Times NbTopsFan (which is apprioxiamately the fequency the fan is spinning at) by 60 seconds before dividing by the fan's divider
   
if (Calc < 1200)
{
   Serial.print (Calc, DEC); //Prints the number calculated above
  Serial.print (" rpm\r Button State: "); //Prints " rpm" and a new line
  Serial.print(currentState);
  Serial.print(" ldr reading = ");
Serial.print(ldr);
Serial.print("\n");
}
}


void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

As I'm posting this, I realise I have no idea how it works as I've been using analogWrites(transistor, dutycycle) the whole time, and I BY MISTAKE, have a digitalWrite(transistor,dutycycle), where I tried to create an interval to get the RPM. If I were to replace the digitalWrite with an analogWrite, this code wouldn't work properly. I indicated the point I'm talking about in the code with a //*****************