Saturday, August 25, 2012

How to measure RPM in a glimpse of an eye

So, yes. I just assembled a setup to measure rotation speed of an RC model motor. It uses a reflective IR sensor to provide high level signal to MCU whenever a blade of a propeller comes by in front of it. OK, we've got a series of impulses with the frequency of motor revolutions divided by number of blades, but how do we know how many rpm does it make?

1. Measuring frequency


This was my very first take on the problem. I decided I could take a time interval, a second for simplicity, and just count how many times blades made the sensor go off. Divide than by number of blades and multiply by 60 (seconds in a minute) to get your rpm. Once you do that, you will immediately notice, that the result comes in multiples of 60s. If you had 10 rps (revolutions per second), you calculate for 600 rpm, 11 rps account for 660 rpm. This is good +=5% error, and it's called discretization error. Surely the higher the speed, the lower this error is, but it's still very annoying. Another error comes from instability of revolution period. This also create a lot of trouble if the rotation speed is low.

More here, if you really take 1 second interval, the whole system does not respond very well - you may want to have refresh rate at least 3 times per second... And the shorter the measurement interval is, the bigger is discritization (if you have 330ms, the result will come now in multiples of 180!) error and the higher is the minimum rotation speed you can measure (for minimum of 3rps - for wich you'll have yet to fight, I'd say it's rather 6 rps with 330ms period - you'll have 180 rpms) - which is not a very huge problem, but still lousy.

There must be another method, I thought, that allows e.g. www.HobbyKing.com to create Turnigy Multi-Blade Micro Tachometer and claim minimum rpm of 10 rpm as well as high precision of measurement. And there's one. Thinking of it now, I believe it's a trivial and obvious solution. However I had to read a book on measurements to get to this.

2. Measuring period


That's it and that's really easy. Instead of counting how many blades will pass sensor in time period, you rather count how many time periods pass in between two blades crossing sensor FOV. I initially set up MCU to count 1us intervals, but found out my 16 bit counter overflows too fast and I can't measure low rpm. So I just used prescaler.

It's quite easy to reverse-calculate all the parameters here. Imagine you need to go as low as 10rpm as Turnigy tachometer does. So you need to setup MCU so 16 bit counter overflows in longer than 6s (10 rpm is 0,167rps or 1/0,167 = 6 seconds per revolution). That is 6/65536 = ~92us per timer clock.
Now, what is the highest speed you can measure? With e.g. 1% precision you may want to have at least 100 timer clocks per revolution, i.e. 9,20 ms. That is 108 rps or 6522 rpm.

And that calculation is for 1 blade propeller (that are not often met in the wild). If there are two blades the calculation above will be for 5rpm min and 3260 rpm max.

What's the estimated error? Absolute maximum error for this method is not higher than 1 timer clock (92us). For 10rpm error is 92us/6s. Which is tiiiiinyyyy. Not noticeable. Means not relevant. For 6522 rpm it's 92us/9.2ms = 1%

Now you can almoset instantly measure rpms with high enough accuracy. Oh, you might think 6522 rpm (3260rpm for a two-blade propeller) is nonsense? Tiiiiinyyyy? Not noticeable? Means not relevant? Well, tell me how to handle higher rpm in comments.

Happy measuring!
Cheers!

P.S. Oh, nearly forgot. Here's some code for PIC:

#include "htc.h"
#include "freq_meter.h"
#include "hw_configuration.h"
#include <stdio.h>
#include "lcd.h"

__CONFIG(FOSC_INTOSC & CLKOUTEN_OFF & WDTE_OFF & PWRTE_ON & PLLEN_ON);

#define TMR0_LCD_REFRESH_RATE 5
unsigned char outputBufferStr1[9];
unsigned char outputBufferStr2[9];
unsigned char outputBuffer[40];
unsigned int TMR0_OverflowCounter = TMR0_LCD_REFRESH_RATE;
bit outputResultFlag = 0;
bit peakHoldFlag;
bit errorFlag = 0;
unsigned int cntval = 0;
unsigned int maxcntval = 0;
unsigned long rpm = 0;
unsigned long maxRpm = 0;

void main (void)
{
    picSetup();
    LCD_Init ();

    TMR1H = 0;
    TMR1L = 0;

    peakHoldFlag = 1;

    LCD_WriteStr2 ("Hi!");
    while(1)
    {   
        if (outputResultFlag)
        {
            //LCD_ClearAndHome();
            if (errorFlag)
            {
                RB3 = 1;
                LCD_WriteStr1 ("  Error!");
            } else
            {
                RB3 = 0;
                rpm = 15000000/cntval;
                if (rpm > maxRpm) maxRpm = rpm;
                sprintf(outputBufferStr1, "%5urpm", (unsigned int)rpm);
                LCD_WriteStr1 (outputBufferStr1);
            }
            if (peakHoldFlag)
            {
                sprintf(outputBufferStr2, " pk%5u", (unsigned int)maxRpm);
                LCD_WriteStr2 (outputBufferStr2);
            }
            outputResultFlag = 0;
        }
    }
}

void interrupt isr (void)
{
    if (TMR1IE && TMR1IF)
    {
        TMR1IF = 0;       
        errorFlag = 1;
    }

    if (CCP1IE && CCP1IF)
    {
        CCP1IF = 0;
        if (errorFlag)
        {           
            TMR1L = 0;
            TMR1H = 0;
            cntval = 0;
        }
        else
        {
            cntval = (CCPR1H << 8) + CCPR1L;
            TMR1L -= CCPR1L;
            TMR1H -= CCPR1H;
        }
        errorFlag = 0;
    }

    if (TMR0IF)
    {
        TMR0IF = 0;
        if (TMR0_OverflowCounter-- == 0)
        {
            outputResultFlag = 1;
            TMR0_OverflowCounter = TMR0_LCD_REFRESH_RATE;
        }
    }
    
}


No comments: