Newbie here so please correct me if I'm wrong, be it wrong post category or any other methodology/code errors described below.
So I tried to control some PWM fans using Arduino. I'd like to also know their RPM.
Based on Noctua, Sanyo Denki, and Nidec (sorry the Nidec link being NOT sourced from Nidec but rather some third-party vendors), I believe that all these small form factor (in particular 120*25/120*38) fans use the same method for reporting RPM: one full rotation corresponds to two square waves, using open collector output (so external pull-up required), s.t. we could attach a IRQ with falling-edge trigger to increment some counter, grab the counter (with proper critical section handling i.e. turn on/off interrupt) after some predetermined time, and convert it to actual RPM with some simple math.
And it does work, to some extent: some models of fans report reasonable RPM similar to what I'd observe when they're on a PC (RPM read through motherboard bios), some models only report reasonable RPM when either 0% or 100% duty but with any other duty it's like around 2 to 3 times that of expected, while still some other models report erratic numbers all over the place even when the fan itself is running at constant speed (by which I mean duty is constant and one should expect some plus or minus several tens of rotations at most but instead it's like randomly jumping over range of several thousands).
Here's the relevant part of my code (Arduino Nano):
/*
* For Timer2 OC2B for 25 kHz PWM
*/
static const uint8_t PinTachometer = 2; // Interrupt on Arduino Nano
static const uint8_t PinPWM = 3; // Timer2 OC2A on Arduino Nano
static const auto TCCR2A__ = _BV(COM2B1) | _BV(WGM20); // Phase Correct PWM allows for 0% duty
static const auto TCCR2B__ = _BV(CS21) | _BV(WGM22); // No scaling (16 MHz), use OC2A to fine tune resulting freq.
static const unsigned OCR2A__ = 79; // Both for PWM duty granularity and down-scaling to 25 kHz.
static const unsigned DEFAULT_PWM_DUTY = OCR2A__ / 4;
/*
* Above is Timer2 OC2B PWM config.
*/
static const unsigned long DelayTime = 20;
static volatile unsigned long falling_edges = 0;
void tachometer_isr() {
falling_edges += 1;
}
void setup() {
pinMode(PinPWM, OUTPUT);
pinMode(PinTachometer, INPUT);
// I have a 22k pull-up resistor connected to +5V since most fan spec
// claims they use open-collector setup.
attachInterrupt(digitalPinToInterrupt(PinTachometer), tachometer_isr, FALLING);
/* Setup PWM Output
*
* Sanyo Denki fans use 25 kHz signal to do PWM
* whereas Arduino Nano by default uses 490/980 Hz depending on which pin.
*
* Use Phase Correct PWM
* since on Arduino Nano Fast PWM doesn't allow 0% PWM
* without some hacking: need to turn off PWM to do that.
* It's possible but just more work.
*
* Here we use Timer2 since Timer0 is used for clock for several internal
* library functions while Timer1 is used for the Servo library.
*/
// Override OC2A to Phase Correct PWM Mode
TCCR2A = TCCR2A__;
// No scaling, Arduino is 16M
// Phase Correct, OCRA as TOP s.t. output is 25K.
TCCR2B = TCCR2B__;
OCR2A = OCR2A__;
OCR2B = DEFAULT_PWM_DUTY; // initial PWM set at around 25% duty
}
void loop() {
static const unsigned clock_period = 72;
static unsigned clock = 0;
if (0 == clock) {
noInterrupts();
const unsigned long fes = falling_edges;
falling_edges = 0;
interrupts();
// `delay()` is in milliseconds, 2 falling edges per rotation
const unsigned long rpm = (fes * 30000ul) / ((unsigned long)clock_period * (unsigned long)DelayTime);
// `tm1637` is, as the name indicates, TM1637 7-segment display.
// I got a 4-digit one so typical usage range should be covered.
// library used is [avishorp](https://github.com/avishorp/TM1637)
// its initializing code are omitted for brevity
tm1637.showNumberDec((int)rpm, true);
}
clock = (clock < clock_period - 1) ? clock + 1 : 0;
delay(DelayTime);
}
I believe that Arduino Nano should be able to handle interrupt orders of magnitude higher than required serving as tachometer, and besides some fans do always report as expected, so I'm unable to tell if there's some catch in my code.
I'm suspecting that maybe those fans with erratic readings are those of which internal open collector circuits cannot source enough current to make stable output and/or there's some sort of debounce required, but I'm not sure how to implement circuit to help mitigating the issue.