4

I have the following circuit:

enter image description here

(The Arduino is powered via the USB)

The problem is that I the reading from the fan's tachometer signal are incorrect,

I tried to add the two resistors as voltage dividers but still the readings are wrong. When I check the voltage of the tachometer signal with a multimeter, in the time the multimeter is connected the readings are somewhat correct (not 100%).

Here is a snippet from the fan's documentation: enter image description here

(BTW: When I used a different 12v pwm fan and didn't use a voltage divider the readings were incorrect only in the time I switched the 12v power off until the fan stopped spinning (after it stopped spinning the tachometer stopped sending signals so the rpm was 0 which was correct).)

Working code (correct tacho signal value):

const byte tachpin = 2;

unsigned int tacho = 0;
long int startTime = 0;
long int elapsed = 0;

void setup() {
  Serial.begin(9600);
   // Configure Timer 1 for PWM @ 25 kHz.
    TCCR1A = 0;           
    TCCR1B = 0;           
    TCNT1  = 0;           
    TCCR1A = _BV(COM1A1)  
           | _BV(COM1B1)  
           | _BV(WGM11);  
    TCCR1B = _BV(WGM13)   
           | _BV(CS10);   
    ICR1   = 320;         



    pinMode(tachpin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(tachpin), tachisr, RISING);
    startTime = millis();
}

void tachisr(){
  tacho++;
}

void loop() {
      elapsed = millis() - startTime;
      if(elapsed > 1000) {
        int tachcopy = 0;
        noInterrupts();
        tachcopy = tacho;
        tacho = 0;
        interrupts();
        rpm = (tachcopy / 2.0) * (60000 / elapsed);
        Serial.println(rpm,DEC);
      }
      startTime = millis();
}

Correct reading from working code

rpm = 0, tachcopy = 0
rpm = 1150, tachcopy = 39
rpm = 2360, tachcopy = 80
rpm = 2655, tachcopy = 90
rpm = 2773, tachcopy = 94
rpm = 2655, tachcopy = 90
rpm = 1268, tachcopy = 43

Not working code (incorrect tachometer signal):

const byte tachpin = 2;

unsigned int tacho = 0;
long int startTime = 0;
long int elapsed = 0;
long int rpm = 0;
unsigned int pwmValue = 0;
unsigned int outValue = 0;
unsigned int seconds_counter = 0;

void setup() {
  Serial.begin(9600);
   // Configure Timer 1 for PWM @ 25 kHz.
    TCCR1A = 0;           
    TCCR1B = 0;           
    TCNT1  = 0;          
    TCCR1A = _BV(COM1A1)  
           | _BV(COM1B1)  
           | _BV(WGM11);  
    TCCR1B = _BV(WGM13)   
           | _BV(CS10);  
    ICR1   = 320;        

    // Set the PWM pins as output.
    pinMode( 9, OUTPUT); //!!!ADDED LINE!!! 



    pinMode(tachpin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(tachpin), tachisr, RISING);
    startTime = millis();
}

void tachisr(){
  tacho++;
}

//!! ADDED FUNCTION !!!
void analogWrite25k(int value) 
{

    OCR1A = value;  
    
}

void loop() {

      elapsed = millis() - startTime;
      if(elapsed > 1000) {
        int tachcopy = 0;
        noInterrupts();
        tachcopy = tacho;
        tacho = 0;
        interrupts();
        startTime = millis();
        rpm = (tachcopy / 2.0) * (60000 / elapsed);
        Serial.print("rpm = ");
        Serial.print(rpm,DEC);
        Serial.print(", tachcopy = ");
        Serial.println(tachcopy,DEC);

        seconds_counter++;
      }

      analogWrite25k(100); //!!!ADDED LINE!!!

      
}

Not working code output:

rpm = 737854, tachcopy = 25012
rpm = 738591, tachcopy = 25037
rpm = 737824, tachcopy = 25011
rpm = 738591, tachcopy = 25037
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737824, tachcopy = 25011
rpm = 738591, tachcopy = 25037
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012

Readings when using 10K ohm resistor as pull up (instead of the voltage divider (10K+4.7K)) and maxing out analogWrite25k - analogWrite25k(320);:

rpm = 6165
rpm = 6401
rpm = 6136
rpm = 6136
rpm = 6165
rpm = 6018
rpm = 6283
rpm = 5870
rpm = 6047
rpm = 6578
rpm = 6460
rpm = 6224
rpm = 5782
rpm = 6047
rpm = 6136
rpm = 6136
rpm = 6313
rpm = 5988
rpm = 6224
rpm = 6195
rpm = 6136
rpm = 5900

Which is interesting because the max speed of the fan is ~3000rpm (so rpm readings are about x2 max fan rpm when maxing out analogWrite25k)

Update:

Added debounce + 10K Ohm to Arduino 5V:

#define DEBOUNCE 10
//Interrupt handler. Stores the timestamps of the last 2 interrupts and handles debouncing
unsigned long volatile ts1=0,ts2=0;
//Calculates the RPM based on the timestamps of the last 2 interrupts. Can be called at any time.
unsigned int calcRPM(){
    unsigned long ts1_copy, ts2_copy;
    noInterrupts();
    ts1_copy = ts1;
    ts2_copy = ts2;
    interrupts();

    Serial.print(ts1_copy);
    Serial.print(" / ");
    Serial.print(ts2_copy);
    Serial.print(" / ");
    Serial.println(ts2_copy - ts1_copy);   
    return (60000000.0 / (ts2_copy - ts1_copy ) / 2.0);
}

void tachisr2() {
    unsigned long m=micros();
    if((m-ts2)>DEBOUNCE){
        ts1=ts2;
        ts2=m;
    }
}

Output after adding debounce:

rpm = 2953.92
90923308 / 90923320 / 12
rpm = 2500000.00
91948328 / 91958496 / 10168
rpm = 2950.43
92994188 / 92994200 / 12
rpm = 2500000.00
94031032 / 94031040 / 8
rpm = 3750000.00
95047108 / 95057256 / 10148
rpm = 2956.25
96093308 / 96093320 / 12
rpm = 2500000.00
97119144 / 97129296 / 10152
rpm = 2955.08

When the value is ~2900 it is the correct value.

Update 2

Hope you can help me with another problem, The rpm value is scattering on a range of up to 120rpm+ even on low fan cycle of 800 rpm, of course when the elapsed time wait is bigger the scattering range is lower, the rpm reading might be not precise by about ~10%, I saw videos of live speed reading from BIOS of PC fan connected to PC motherboard and some of the fans are more precise (maybe because of BIOS code? (maybe there is a standard that specifies minimum time before reading the tacho counter?)), The question is are the tachometer sensors on 12V PC fans not supposed to be super precise by design or do I still have some problems with my circuit?, If I want a more precise rpm reading should I use an external tachometer sensor? which one is the best? optical/magnet based? (btw my current circuit doesn't include the 4.7KOhm resistor from the schematic, only 10KOhm pull up to 5V)

4
  • Please show the code, that gave you wrong values and explain how exactly they are wrong and how you veryfied the correct values
    – chrisl
    May 23, 2023 at 13:32
  • @chrisl, added the code, I know the values are wrong because I get >10,000 as the rpm and sometimes negative numbers
    – Takata
    May 23, 2023 at 14:45
  • @jsotola its the voltage divider, I also tried 10K pull-up connected to 3.3V without the voltage divider and also got incorrect readings
    – Takata
    May 23, 2023 at 14:46
  • @jsotola, Check my update please, I figured that the problem occurs only when I enabled the pwm output pin.
    – Takata
    May 23, 2023 at 15:54

1 Answer 1

1

1. Open Collector Output of a module.

The power for the open collector output should come from the MCU, not (indirectly) from the sensor's power supply. It is very similar to wiring a push button switch on the low side. Usually, the internal pullup resistor of the Arduino pin which is connected to the module passes enough power to detect the status of this output (as you have done). This internal pullup resistor has a value of around 30k. It is also possible to add a lower value external pull up resistor, if required say because of electrical noise, between the Arduino's 5v power rail and the pin. Ensure, however, that the current is within the tolerance of the connected device.

Measuring PWM voltages with a multimeter will not be successful, as you have discovered. These tend to integrate and show an average voltage. Say 12 volts at a 50% duty cycle will be read as 6 volts. Use an oscilloscope to see the voltage across the wave form.

2. Electrical Interference.

Your figure here tachcopy = 25012 indicate that your 25KHz signal is leaking through and being detected as pulses from the sensor. Powering the open collector via the Nano's 5v power rail (as above) may eliminate this. Maybe also better decoupling on the 12 volt power supply (say adding a big capacitor).

3. Code Issues

Your code has an number of issues which should be cleaned up. Some may show up after only about 25 days. Some may show up after the next recompilation and some affect the accuracy of the results.

  • unsigned int tacho = 0; should be volatile.
  • int tachcopy = 0; should be unsigned int to match the data tape it is copying.
  • Variables which are derived from millis() should be unsigned long (uint32_t) as here: elapsed = millis() - startTime;.
  • This (60000 / elapsed) should be forced to a floating point division say (60000.0 / elapsed)
  • This could be in setup() instead of loop(): analogWrite25k(100); //!!!ADDED LINE!!!

4. "rpm readings is about x2 max fan rpm"

I can't explain that. It may be that the sensor output needs to be debounced.

5. Sample debounce routine (lockout principle)

// Simple debounce routine. Based on original code.
// After accepting a pulse locks out further pulses for 3 ms
// Should be OK for 100Hz (period 10ms)

void tachisr(){
  // ISR (external interrupt)
  static uint32_t lastAcceptedPulseAtMs = 0 ; // static initialised once only
  uint32_t ms = millis() ;
  if ( ms - lastAcceptedPulseAtMs >= 3 ) {  // 3 ms - tune if required
    lastAcceptedPulseAtMs = ms ; 
    tacho++;
  }
}

Note: An ISR should be quick to execute and should not contain Serial.print() etc.

13
  • Yes, debouncing is a must. It sometimes also has positions that are "oscillating" even if it's not running (4-pin fans with 0% pwm)
    – KIIV
    May 24, 2023 at 14:33
  • @KIIV I did a quick search but could not discover what technology is used for the tachometer. Some are very prone to bouncing say a reed switch, however, I doubt if that would last very long in such an application. A hall effect sensor or something optical would be less of a risk. Maybe the OP can tell us what their model uses.
    – 6v6gt
    May 24, 2023 at 15:46
  • @6v6gt Thanks for your great answer, I will test it in a bit, However I don't understand why tacho need to be volatile? (i'm accessing it only when interrupts are off and one time in ISR). Regarding your question, I don't have access to the FAN controller but My fan is Noctua NF-F12.
    – Takata
    May 24, 2023 at 16:02
  • @Takata tacho needs to be volatile, because the "source of truth" is in memory, not in the CPU registers. For "normal" variables, the compiler can switch the source of truth between memory and registers as it pleases. But in your case, without volatile, the register containing the tacho value while being in loop() is assigned and filled from memory at the start of loop(), which is before you call noInterrupts(). volatile basically adds machine instructions to LOAD the value from memory into a register in the moment you read the variable in your code (and STORE directly after writing)
    – orithena
    May 24, 2023 at 16:28
  • 1
    @Takata I've just noticed that the interrupt trigger is the RISING edge in your code. For open collector output it should really be FALLING. The falling edge is cleaner because the transistor at the output pulls down sharply when switched on. When switched off, the output floats up due to the pullup resistor and any capacitive effect of the sensor. Whether it makes any real difference in your case I don't know.
    – 6v6gt
    May 25, 2023 at 4:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.