Best Practice for Counting RPMs

I know this seems like a very trivial question (and hopefully it is) but I’m trying to figure out the best way to get accurate RPMs from Hall effect sensor readings. Through my scope, I can see a fairly clean square wave and I’ve determined that I’m getting two highs for every revolution. So, from the scope using the time interval I think I can very accurately calculate the RPMs. However, using software to count the interrupts I’m not able to get quite the same number. I’ve tried two different approaches and I’m curious to know if there are others that might yield better results. The results of these two approaches can differ by as much as a couple hundred RPMs and neither match my calculated result.

Approach #1:

  1. Add an interrupt handler that increments a static variable on every fire of the sensor.
  2. Setup a Timer that fires every second.
    2a. Calculates RPMs as count * 60.
    2b. Resets counter.

Problem with Approach #1: Based on Timer which is not guaranteed to be accurate.

Approach #2:

  1. Add an interrupt handler that calculates RPMs on every fire based on time since last interrupt.
    1a. Calculate ms since last interrupt.
    1b. Calculate RPMs based on 1a.
    1c. Store “last time” for 1a calc on next fire.

Problem with Approach #2:

  1. I feel like there’s too much going on in every call.
  2. I’m not sure that using the DateTime difference for one interrupt gives me enough precision to accurately calculate RPMs based on one revolution. I’d rather have a larger sample.

What other approaches are people using? Or am I just crazy and approach #2 is really good enough? Sorry for not providing exact measures & code samples. I’ll dig those up this evening if needed. I’m really more interested in conceptual ideas at the moment.

I am going to be doing the same thing in the next week. My approach is going to be just like approach #1 except I will be using a FIR-type filter to smooth out the RPM reading. So there will be an array of 10 RPM values stored as ints in a queue object, and each second I will .enqueue the latest reading onto the queue, and call dequeue which will remove the oldest from the collection, as long as the count is > than 10. My display thread will look at this queue, looping thru it while discarding the highest and lowest values, and average the remaining 8. I have used this type of filtering before and it works great. The longer the time constant the smoother the value but obviously the more of a delay there will be in averaging RPM changes.

Bob, I definitely understand the benefits of that approach for some applications but I don’t think a 10 sec average is going to be especially useful for me since I would like to be able to monitor fan speed in near realtime as I increase/decrease the speed. In 10 seconds I could go from min to max and back to min again. Averaging that wouldn’t make for a particularly interesting plot.

Also, my basic concern with approach #1 is that as I understand it in NETMF the Timer can’t be guaranteed to fire at exactly 1s. It could be off by more than 20% due to GC and other factors. However, I think I may have just figured out the best of both worlds while typing this…

Approach #3:

  1. Add an interrupt handler that increments a static variable on every fire of the sensor.
  2. Setup a Timer that fires every second.
    2a. Calculate rev/ms as count / (diff in interrupt “time” and “last time”). Convert to RPMs…
    2b. Resets counter.
    2c. Update “last time”.

This would basically average over ~ 1sec but doesn’t depend on the Timer being absolutely accurate. Can I expect the “time” parameter in the event handler to be absolutely accurate?

I should add…I will use RLP to do the interrupts. My time constant is relatively slow but that’s easy to adjust. Its a tradeoff between responsiveness and smoothness. I think handling the datetime in #3 is best since it takes into account any latency in the timer interrupt.

Well, now that’s just cheating… :wink:

A big problem with #3 that I just realized now that I’m looking at the code again. The event handler for a timer doesn’t have the “time” parameter. I was thinking of the event handler for an interrupt. So, that blows #3 out. However, now I have a #4 option which should suffice.

Approach #4:

  1. Add an interrupt handler that increments a static variable on every fire of the sensor.
  2. Capture “last time” = “time” of first interrupt.
  3. Count n interrupts.
  4. On nth interrupt, calc RPMs based on diff of “time” of last interrupt minus “last time”.

I believe this is going to yield an optimal solution that’s fairly well averaged and with minimal processing within the interrupt. I’ll get to try it within an hour and will let you know how it turns out.

i think i would approach this like 3, using the actual tick count when the interrrupt came in and difference that to your previous sample you want to calc against (so you don’t get any issue because of drift of timer)

@ Brett, I think that’s basically what I described in #4. If there’s something different, please explain. Thanks.

I think thats right - although you were typing #4 as I was too it seems, since it wasn’t there when I started but was once I had replied (and I’ve flown from AUS into west coast US timezone and am somewhat slow at doing things like this at the moment :))

Sorry, maybe not now I think…

At startup, write the LastTimerTime as now().ticks, and revolutions = 0

At each interrupt, Revolutions++ and set LastInterruptTick=interrupt ticks

At 1sec timer,
TimePeriod = LastInterruptTick-LastTimerTime
RPM = revolutions * (TimePeriod) * [magic number to get from ticks to mins]
revolutions =0

Does that make sense?

And your #4 is different in that at slower RPM it will take longer to hit the N to calculate the RPMs, where with the other approach you get a new RPM calc every second (or whatever timer you choose). Some scenarios might suit one way or the other, but it probably isn’t a big deal which way you choose if you have a decent RPM count (what is your specific use of this BTW?).

Brett, welcome to the U.S.! What brings you here?

I think you’ve come up with the best solution yet! I’m about to give it a try now that the kids are in bed :slight_smile: I’m building a fan controller.

yes, but there are fans and there are fans :slight_smile: Do you have a rough approximation of RPM range?

I’m in the US for a technical conference - since I work for Microsoft, it’s in Seattle… Such a pity leaving summer in Sydney for Winter :slight_smile:

I didn’t realize you also worked for Microsoft. Are you an evangelist like Andrew & Pete? Australia’s definitely on my list of places to visit.

This is the fan:

The packaging says…
100% duty = 1200 RPM ± 10%
75% duty = 900 RPM ± 10%
50% duty = 750 RPM ± 10%

It seems to only have three speeds. I was hoping for more granular control but this will suffice. Also, it seems that I can’t turn it off through PWM which is a shame. I may keep looking for better fans but I’m going to try and make due with this one for now.

Now I’m not sure I’m actually getting interrupts for every rise or maybe it’s just the VS debugger… What’s the min voltage needed to trigger an interrupt on a Panda? Here’s what my signal looks like. I’m getting long periods of 0 RPMs. If I put a debug break in the interrupt event handler then there can be long periods between when it’s actually raised. Am I going to have to amplify this?

Evangelist, no, my role is VERY FAR from anything to do with development - in fact, those two alone probably write more code in their sleep than I can :slight_smile: If you hadn’t gathered based on what I do most of here, I work as part of the support organisation :slight_smile: :slight_smile:

Is that really only .8v? Mmm, reliably I think you need more… I can’t remember what the datasheet says, but that’s where you’re going to have to find that answer i think…

I’m afraid so :frowning:

What sort of support? I thought I had you pegged as a EE.

mmm, .8v then you will be in trouble. I haven’t checked the spec sheets lately but I know LPC2388 (usbizi 144 for domino) has a minimum “high” voltage of 2.0v, and a maximum “low” of 0.8, so that’s possibly why you’re seeing weirdness. Which board are you using?

Mmm, an EE, well the truth is I am pretty far from it. I have no formal training in EE except I did one EE subject while doing BMath (CompSci). I did have a lot of interest in this kind of thing as a kid, learnt a lot along the way thru the help of my dad (an electrician). I have not a great deal of programming expertise, certainly not in real world outside uni. My role is actually a Technical Account Manager, which despite the title doesn’t need technical expertise although I have previously (10+ yrs ago) been in technical roles; my Account Management role is more about managing support escalations and relationships with cusotmers.

In the middle of adding transistors to the circuit to bump the signals up to 3V3… Hopefully, that’s going to straighten things out.

I’m always amazed at how many “non tech” people have tech as a hobby. Enjoy your conference.

It is unfortunate that an entire generation growing up in the 80’s got the message “get a real job”… Like being a banker maybe? Good move peeps. If someone applied Ohm’s law to the economy maybe there would be some balance today…

The world needs engineers to accountants at a 1000000:1 ratio :slight_smile:

Anyway… Back to the topic… “Best practice” may not be the best idea. I think us tinkerers are discovering lovely gems of innovation though blissful ignorance of “best practice”. What better way to learn than through experience.

I’d suggest using a schmitt triger opto, like the PS2501 along with an opamp to drive the opto. That way you have good isolation and there’s little opportunity for the fan to fry something.