Funny readings when trying to read PWM fan RPM

I'm working on a project that will test PC cooling fans - both 3-pin non PWM type and 4-pin PWM type. It will adjust 3-pin types via voltage and 4-pin via PWM obviously. It will display fan RPM, PWM% and Voltage on the LCD. It works perfectly (still need to add the voltage part of the code) with 3-pin fans but on most PWM fans the RPM reads way too high (like 9k+ RPM) if the PWM is anything but 0 or 100. The only PWM fans I've found that work correctly are the stock Intel CPU heat sink fans. Those read correctly no matter the PWM setting. Any thoughts as to what could be causing it?

Here's the code so far (still need to add the voltage reading portion of it):

// Code for PC fan testing unit
// By Will Lyon - http://www.computersandcircuits.com

#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 8, 9);

volatile int NbTopsFan;
int Calc;
const int fan1 = 2;
const int PotPin = A0;
const int pwmPin = 10;
const int voltPin = A2;
int fanPwm;
int val;
int pwm;

typedef struct{
  char fantype;
  unsigned int fandiv;
}fanspec;

fanspec fanspace[3]={{0,1},{1,2},{2,8}};

char fan = 1;
void rpm ()
{
  NbTopsFan++;
}

void setup()  {
  pinMode(fan1, INPUT);
  pinMode(PotPin, INPUT);
  pinMode(voltPin, INPUT);
  attachInterrupt(0, rpm, RISING);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print(" Computers  And ");
  lcd.setCursor(0, 1);
  lcd.print("    Circuits    ");
  delay(2000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(" Computer   Fan ");
  lcd.setCursor(0, 1);
  lcd.print(" Testing   Unit ");
  delay(2000);
  lcd.clear();
}

void loop()  {
 NbTopsFan = 0;
 sei();
 delay (1000);
 cli();
 Calc = ((NbTopsFan * 60)/fanspace[fan].fandiv);
 lcd.setCursor(0, 0);
 lcd.print ("RPM");
 lcd.setCursor(5, 0);
 lcd.print ("PWM%");
 lcd.setCursor(12, 0);
 lcd.print ("Volt");
 lcd.setCursor(0, 1);
 if (Calc < 1000) lcd.print (' ');
 if (Calc < 100) lcd.print (' ');
 if (Calc < 10) lcd.print (' ');
 lcd.print (Calc);
 lcd.setCursor(4, 1);
 lcd.print (" ");
 pwm = analogRead(0);
 fanPwm = map(pwm, 0, 1023, 0, 255);
 analogWrite(pwmPin, fanPwm);
 val = map(fanPwm, 0, 255, 0, 100);
 lcd.setCursor(5, 1);
 if (val < 100) lcd.print (' ');
 if (val < 10) lcd.print (' ');
 lcd.print(val);
}
 sei();
 delay (1000);
 cli();

Wrong!

You should be using millis() to determine when a suitable interval has passed, and then compute the RPM. You should NOT be disabling interrupts.

PaulS:

 sei();

delay (1000);
cli();



Wrong!

You should be using millis() to determine when a suitable interval has passed, and then compute the RPM. You should NOT be disabling interrupts.

That's the code I've been using without problems for a few years now so I kept on using it. How should I do it then?

look up http://arduino.cc/en/Tutorial/BlinkWithoutDelay

Henry_Best:
look up http://arduino.cc/en/Tutorial/BlinkWithoutDelay

I'm not sure if I did it correctly but it's still not working properly. I seems to read properly only at 0% and 100% PWM setting. The fan I'm using now shows ~660 at 0% as it should and ~2100 @ 100% as it should. If I bump it up to 3% it's up around ~9k and at 99% it shows ~over 9k as well.

Here's the code as of right now:

// Code for PC fan testing unit
// By Will Lyon - http://www.computersandcircuits.com

#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 8, 9);

volatile int NbTopsFan;
const int fan1 = 2;
const int PotPin = A0;
const int pwmPin = 10;
const int voltPin = A2;
int Calc;
int fanPwm;
int val;
int pwm;
long previousMillis = 0;
long interval = 1000;

typedef struct{
  char fantype;
  unsigned int fandiv;
}fanspec;

fanspec fanspace[3]={{0,1},{1,2},{2,8}};

char fan = 1;
void rpm ()
{
  NbTopsFan++;
}

void setup()  {
  pinMode(fan1, INPUT);
  pinMode(PotPin, INPUT);
  pinMode(voltPin, INPUT);
  attachInterrupt(0, rpm, RISING);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print(" Computers  And ");
  lcd.setCursor(0, 1);
  lcd.print("    Circuits    ");
  delay(2000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(" Computer  Fan  ");
  lcd.setCursor(0, 1);
  lcd.print(" Testing   Unit ");
  delay(2000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print ("RPM");
  lcd.setCursor(6, 0);
  lcd.print ("PWM");
  lcd.setCursor(11, 0);
  lcd.print ("Volts");
  lcd.setCursor(4, 1);
  lcd.print (" ");
  lcd.setCursor(8, 1);
  lcd.print ("%");
}

void loop()  {
 
  // PWM display info
  pwm = analogRead(0);
  fanPwm = map(pwm, 0, 1023, 0, 255);
  analogWrite(pwmPin, fanPwm);
  val = map(fanPwm, 0, 255, 0, 100);
  lcd.setCursor(5, 1);
  if (val < 100) lcd.print (' ');
  if (val < 10) lcd.print (' ');
  lcd.print(val);
  
  // Voltage display info
  lcd.setCursor(11, 1);
  float h = analogRead(voltPin) * 17.211 / 1023;
  lcd.print(h, 2);
  
  // RPM display info
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval){
    previousMillis = currentMillis;
    digitalRead(fan1);
  NbTopsFan = 0;
  sei();
  delay (1000);
//  cli();
  lcd.setCursor(0, 1);
  Calc = ((NbTopsFan * 60)/fanspace[fan].fandiv);
  if (Calc < 1000) lcd.print (' ');
  if (Calc < 100) lcd.print (' ');
  if (Calc < 10) lcd.print (' ');
  lcd.print (Calc);
  }
}
    digitalRead(fan1);

Why? If you don't want the value that digitalRead() returns, why ask for it?

  sei();
  delay (1000);
//  cli();

This is still crap.

Take a look at this page and it might help you understand what is happening.
http://www.bearblain.com/fan_speed_control.htm

The 3-wire fan (quarter way down the page) uses the "+" input as the power supply for the tach output through resistor R1. If you use PWM on the "+" input, the tach output will also go high and low at the PWM frequency.

The 4-wire fan (halfway down the page) has a separate input for the PWM pulses, and the power supply is used for the hall effect transistor and tach output.

PaulS:

    digitalRead(fan1);

Why? If you don't want the value that digitalRead() returns, why ask for it?

  sei();

delay (1000);
//  cli();



This is still crap.

Well if I don't read interrupts don't I need digitalRead instead?

SurferTim:
Take a look at this page and it might help you understand what is happening.
Fan Speed Control

The 3-wire fan (quarter way down the page) uses the "+" input as the power supply for the tach output through resistor R1. If you use PWM on the "+" input, the tach output will also go high and low at the PWM frequency.

The 4-wire fan (halfway down the page) has a separate input for the PWM pulses, and the power supply is used for the hall effect transistor and tach output.

I'm not using PWM for the 3-wire fans, those will be adjusted via voltage. Only the 4-wire ones will use the PWM as they should. Any 3-wire fan I plug in has no issue with the RPM reading. If I lower the voltage the reading drops accordingly. It's only PWM (4-wire) fans that I'm having the issue with the funky readings so it's got to be something with the PWM. The 3-wire fan sI've used seem to fluctuate by a couple hundred RPM (~1200 ~ 1400) but not nearly as bad as the PWM fans (~1100 ~ 9200).

Well if I don't read interrupts don't I need digitalRead instead?

Only if you care about what you are reading. If you did, you'd assign the return value to a variable.

By the way, PWM relies on interrupts. Disabling them screws with PWM.