Need math help with tachometer that calculates rpm w/1 cycle.

I am looking for some help with calculating rpm from a single
cycle using (T1) input capture with either a UNO or a Mega.

Basically I want to take a timer count => convert it into micro-
seconds (.06 to .006666) => 60/ micro-seconds = rpm.

With an Arduino, I may need to use the Timer1 8x prescaler and
work with 500ns units.

My tachometer project is for a 2-cycle race engine. The tach will
measure the rpm using several different methods: (1). Capacitive
pick-up attached to the spark plug wire. I have 2 methods to clean
up the ringing associated with the coil. (2). A tach source wire from
the CDI ignition. (3). A motorized Hall Effect test stand. I also have
an ignition coil test stand.

I started doing some Arduino math experiments using the “double”
var size and had what I believe to be rounding errors before I really
made any progress. I tried the FP64lib, but I didn’t understand it. I
also looked into converting values to scientific notation and then apply
my division.

I have tested many individual parts of my tach, but the math and the
circuit to mask the coil ringing are still on my todo list.

I want to get my math to work with a single cycle first, then record
several cycles, determine an average, and calculate the rpm.

Thanks

Bill M.

This might be easier if I could reduce the clock speed to 1mhz. Has
anyone tried the AVR clock-cycle prescaler in the beginning of their
Arduino program before?

I started doing some Arduino math experiments using the "double"
var size and had what I believe to be rounding errors before I really
made any progress.

There is no double on the AVR platform, "double" simply translates into "float".

What is actually your question? If your question refers to code you should post that code, complete code we can compile.

Why would you want to slow down your clock speed? That will just lead to lower resolution results.

If you use the micros() function to return the number of microseconds what you detect the trigger, you can easily compute your rpm

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  Serial.println("ready");
  
  unsigned long startTime;
  unsigned long endTime;

  float rpm;

  startTime = 42; // usec
  endTime = 100;  // usec

  rpm = 60.0 * 1e6 / (endTime - startTime);

  Serial.print("RPM =");
  Serial.println(rpm,6);
}

void loop() {
  // put your main code here, to run repeatedly:

}

pylon:
There is no double on the AVR platform, "double" simply translates into "float".

I understand what you are trying to say, but I think you did a poor
job of conveying that opinion.

From what I understand both are 4 bytes, but a float stores the variable
in scientific notation. Some of what I read suggested to use doubles over
floats. In my testing floats and doubles gave me my best answers, but
only up to 1/100. Everything goes sideways at 1/1000.

pylon:
What is actually your question? If your question refers to code you should
post that code, complete code we can compile.

I need help with the math.

I don't have any code because the math from my testing doesn't come
close to being correct.

If I had a 1mhz clock, then each clock cycle would be 1 microsecond, and
the idea of taking the reciprocal of the input capture clock cycles, then dividing
60/(by the reciprocal of the input capture clock cycles) = rpm should be straight
forward. But I have ran into at least 2 problems.

(1). Division beyond 1/1000 using variables gives poor (if any) results.

(2). Portability. As soon as I increase my clock speed, or change the prescaler
for input capture, converting the input capture clock ticks into time is difficult
for me.

blh64:
Why would you want to slow down your clock speed? That will just lead to lower resolution results

Thank you for your input!

Because it makes all the math simple. I used to think of clock
speed as Horsepower, and more horsepower is always better.
But you loose simplicity when you have faster clock speeds.

IMO, It (faster clk cycles) is only better, when you can write programs
that needs more clk cycles to collect and process data.

blh64:
If you use the micros() function to return the number of microseconds what you detect the trigger, you can easily compute your rpm

I started with Basic Stamps and Arduino code is similar, in that with
easy code and functions like milli(), come with built in overhead that
might compromise the accuracy.

My code for handling the timers and pins use a lot of AVR GCC. IMO
using the T1 input capture, will maintain the best accuracy possible,
and allow me to add more features (in the future) that I need to
properly tune (a lean running) race engine. Features like filters,
data logging, and acceleration/ deceleration plots to estimate peak
HP rpm and the best (jet ski) impeller.

Thanks

Bill M.

wmazz:
From what I understand both are 4 bytes, but a float stores the variable
in scientific notation.

If both types are 4 bytes, they implement the same. Scientific notation is a human readable output thing, nothing computational. Also 4 byte floats have less significant bits or digits than 4 byte integers have. By converting values from unsigned long int into float you are loosing precision. You better do integer math throughout all time related computations, in detail if the result (rpm) still is an essentially integral value.

What's the time range you expect to get from min to max rpm?
What's better in your case, taking the time between pulses or counting pulses within a fixed time slice?

If I had a 1mhz clock, then each clock cycle would be 1 microsecond, and
the idea of taking the reciprocal of the input capture clock cycles, then dividing
60/(by the reciprocal of the input capture clock cycles) = rpm should be straight
forward. But I have ran into at least 2 problems.

(1). Division beyond 1/1000 using variables gives poor (if any) results.

(2). Portability. As soon as I increase my clock speed, or change the prescaler
for input capture, converting the input capture clock ticks into time is difficult
for me.

I would not be dividing by the reciprocal of a number, because dividing by 1/x is the same as multiplying by x, and division is a slower process than multiplication.

There is no need to change the clock frequency of the board itself, just write the code to adapt to the actual processor frequency, which you can obtain in a sketch from F_CPU (the value of which is defined in the boards.txt file).

DrDiettrich:
What's the time range you expect to get from min to max rpm?
What's better in your case, taking the time between pulses or counting pulses within a fixed time slice?

.06 to .006666 milli-seconds is 1000 to 9000 rpm. I prefer just using 1
revolution, or ~10 consecutive revolutions, then calculate the average
and use the average to calculate rpm.

DrDiettrich:
You better do integer math throughout all time related computations, in detail if the result (rpm) still is an essentially integral value.

I realize integer math is the best option, but I have no experience
with it. I have seen it used many times, and I have been puzzled
as to how it was derived.

david_2018:
There is no need to change the clock frequency of the board itself, just write the code to adapt to the actual processor frequency, which you can obtain in a sketch from F_CPU (the value of which is defined in the boards.txt file).

AVR offers an optional clk speed prescaler register that gives you
the option of reducing the clk speed of a 16mhz crystal by 8x, 16x,
etc. but it must be implemented in the top of the program.

Is this similar to what you are suggesting? I think what you are
suggesting would apply to "Delay()" more than a Timer 1 input
capture interrupt?

david_2018:
I would not be dividing by the reciprocal of a number, because dividing by 1/x is the same as multiplying by x, and division is a slower process than multiplication.

I am not sure if using a reciprocal actually works. Is there a formulae
for taking X number of clock cycles and converting it to a time, like
.06 milli-seconds?

However I don't know a shortcut for RPM = 60/milli-seconds.
(.06 to .006666 milli-seconds is 1000 to 9000 rpm)

Thanks

Bill M.

9000 RPM / 60 = 150 RPS or about 6667 µs per rev, so 60000000 (sixty million) / 6667 = 8999.55 RPM. Accumulate 8 revs worth of µs and divide by 8. Dividing by a power of 2 (2, 4, 8, 16, etc.) is faster than 10, the compiler can just do a right shift instead of a slower binary division. 64 >> 3 = 8.

wmazz:
However I don't know a shortcut for RPM = 60/milli-seconds.
(.06 to .006666 milli-seconds is 1000 to 9000 rpm)

Your formula is off by 1000, should read 60000/milli_seconds. This can be evaluated using basic integer math. Caveat: 60000 should be written as 60000UL in C/C++ to prevent truncation.

Your milli-second constants also look wrong to me. 6000 rpm are 100 rps or 1/100 s = 10ms between pulses.

I'd delay all optimization attempts until the algorithms and formulae are proven correct.

Try looking at this to get your feet wet.

DrDiettrich:
Your formula is off by 1000, should read 60000/milli_seconds. This can be evaluated using basic integer math. Caveat: 60000 should be written as 60000UL in C/C++ to prevent truncation.

Your milli-second constants also look wrong to me. 6000 rpm are 100 rps or 1/100 s = 10ms between pulses.

I'd delay all optimization attempts until the algorithms and formulae are proven correct.

Thank you, we are on the same page. I created a table in
excel just to make sure.

1000 rpm => .060_000 usec
2000 rpm => .030_000 usec
3000 rpm => .020_000 usec
4000 rpm => .015_000 usec
5000 rpm => .012_000 usec
6000 rpm => .010_000 usec
7000 rpm => .008_571 usec
8000 rpm => .007_500 usec
9000 rpm => .006_667 usec
10000 rpm => .006_000 usec

Except for the 60UL vs 60000UL. I have seen constants used
up to 6,000,000 but for some reason that hasn't worked for
me. I get too many zeroes, for example 60UL/.010_000 usec
= 6000 rpm. But 60,000UL/.010_000 usec = 6,000,000 rpm?

This isn't my first tachometer, but it does make me wonder why
we have different approaches to the problem, are my calcs incorrect?

Thanks

Bill M.

There are 1 million µsec in a second, 60 seconds in a minute, so 60 million µsec in a minute. If your motor turned 10 thousand revs in that minute, then each rev took 60 million / 10 thousand = 6000 (6 thousand) µsec. If the RPMs were 2 thousand, then each rev would take 60 million / 2 thousand = 30000 (30 thousand) µsec. So, RPM = 60 million (6 + 7 zeros) / µsec per rev.
BTW: What real number does this represent, .060_000 usec ?

Thank you for your explanation. I have always calculated 1 revolution
a different way, but we both come up with the same answer. Now I
know why.

JCA34F:
BTW: What real number does this represent, .060_000 usec ?

I thought .060_000 usec was easier to read than .060000 micro-seconds.
.060000 is 1 revolution @ 1000rpm, and .006000 is 1 revolution @ 10,000
rpm.

Do you have a suggestion for how to perform division using bit shifting?
60/.060000 = 1000, or some way of performing a different calculation
that gives the same result?

Thanks

Bill M.

I have saved a piece of code, written in basic for the 8 bit, sx28 that uses
bit shifting, but I don’t know how to translate it into something I can use.
Are you or someone willing to look at it?

' RPM.SXB
' -------------------------------------------------------------------------
' Device Settings
' -------------------------------------------------------------------------
DEVICE SX28,OSC4MHZ,TURBO,OPTIONX,STACKX,BOR42
FREQ 4_000_000

' -------------------------------------------------------------------------
' IO Pins
' -------------------------------------------------------------------------
Sensor        PIN RB.0 INPUT SCHMITT ' NOTE RA does not have schmitt option

' -------------------------------------------------------------------------
' Variables
' -------------------------------------------------------------------------
rpm           VAR WORD
pWidth0       VAR WORD
pWidth1       VAR WORD

dividendMSW   VAR WORD
dividendLSW   VAR WORD
overflow      VAR BIT
doneBit       VAR BIT

' =========================================================================
PROGRAM Start
' =========================================================================


' -------------------------------------------------------------------------
' Program Code
' -------------------------------------------------------------------------
Start:
  DO
    ' Measure pulse low time
    PULSIN Sensor, 0, pWidth0
    ' Measure pulse high time
    PULSIN Sensor, 1, pWidth1

    ' Find total pulse time (high + low)
    pWidth0 = pWidth0 + pWidth1

    ' Set dividend to 6,000,000 ($5B8D80)
    dividendMSW = $005B
    dividendLSW = $8D80

    ' Perform 32 bit division (rpm = dividend / pWidth0)
    rpm = 1
    overflow = 0  
    DO
      doneBit = rpm.15
      rpm = rpm << 1
      IF overflow = 1 THEN
        rpm.0 = 1
        dividendMSW = dividendMSW - pWidth0
      ELSE
        IF dividendMSW >= pWidth0 THEN
          rpm.0 = 1
          dividendMSW = dividendMSW - pWidth0
        ENDIF
      ENDIF
      overflow = dividendMSW.15
      dividendMSW = dividendMSW << 1
      dividendMSW.0 = dividendLSW.15
      dividendLSW = dividendLSW << 1
    LOOP UNTIL doneBit = 1
    rpm = rpm << 1
    rpm.0 = overflow
    IF dividendMSW >= pWidth0 THEN
      rpm.0 = 1
    ENDIF

    ' rpm now holds the correct value
    BREAK
    WATCH rpm

  LOOP
END

Please check your figures! You have been told multiple times that your microseconds are far away from reality! Finally we are talking about thousands of rpm, not millions!

Are nowadays coders so far away from performing calculations themselves that they do not realize how wrong their calculator-produced figures are? Where is abacus and slide rule that teach pupils thinking while calculating?

16Mhz = .000,000,0625
64x prescaler => 64 x .000,000,0625 = .000,004 micro seconds (usec).
Timer 1 Input capture output = 15,000 ticks @ 4 usec intervals.
15,000 ticks x 4usec = 60,000 usec.
60,000,000/60,000 usec = 1000 rpm.

Is this correct?

Bill M.

It's very abbreviated, hence not literally correct, but in principle the figures seem correct.

I will take that and try some code, and post it for
criticism.

Thanks

Bill M.

I don't see how an arbitrary input capture value is related to 1000 rpm.

Using short integral numbers of ns, µs or ms will make all figures better readable.

DrDiettrich:
I don’t see how an arbitrary input capture value is related to 1000 rpm.

wmazz:
16Mhz = .000,000,0625ns

64x prescaler => 64 x .000,000,0625 = .000,004 μs.

Timer 1 Input capture output = 15,000 clock ticks @ 4μs intervals.

15,000 ticks x 4μs = 60,000μs.

60,000,000/60,000μs = 1000 rpm.

I estimated the Input Capture Register (ICRn) value of 15,000 clock cycles
because it allowed for a 16bit answer without compensating for an overflow.

But I found through testing that my ICRn values were in the 160 to 240 clock
cycles @64x prescaler.

So, I thought the Serial Library was interfering with the timer1 ICR1 values. So
I switched to using a mega2560 and used timer#4 (ICR4) to record the input
capture values. I started with this basic program modified for Timer4.

/* Basic_Input_Capture_Mega2560_v1.3
 *  
 * 0). Using Mega2560 timer4 because it is not effected by (???)
 * Arduino Serial library's use of timer1. ** But I do not know
 * id there is a Timer1 interrupt priority greater than timer4? 
 * 
 * 1). Confirmed that basic features of Timer#4 ICP does work. 
 */

void setup() 
{
  pinMode(49,INPUT_PULLUP);          // ICP4 pin for Mega2560.
  pinMode(13,OUTPUT);                // LED output to confirm ICP interrupt works. 
  
  TCNT4 = 0;                        // just trying to clear registers, I don't know
  TIMSK4 = 0;                       // if it is necessary.
  TCCR4A = 0;
  TCCR4B = 0;  
  

  TIMSK4 = (1<<ICIE4);              // Timer/Counter_n Input Capture interrupt is enabled
  
  TCCR4B = (1<<ICNC4);               //  Noise Canceler enabled
  
  TCCR4B &=~ (1<<ICES4);            // Falling edge select enabled
  
//  TCCR4B = (1<<CS42);               //   Prescaler n/a
  TCCR4B = (1<<CS41);               //   Prescaler for 64x prescaler 
  TCCR4B = (1<<CS40);               //   Prescaler for 64x prescaler 
  
  
  
  sei();                            // Must be set to activate TIMSK4
}

void loop()
{
  

}
ISR(TIMER4_CAPT_vect)
{      
    PORTB ^= 1<<PB7;            // flash on board led when interrupt is triggered.
}

This code performed as expected.

Bill M.

I am separating this into a few posts to make it more readable.

wmazz:
So, I thought the Serial Library was interfering with the timer1 ICR1 values. So
I switched to using a mega2560 and used timer#4 (ICR4) to record the input
capture values.

So my next step was to include the output from ICR4 to confirm my suspicion
that the Serial library was interfering with timer1 and the ICR1 data.

I am using an old ignition test stand (with a variable speed control) to test the
mega2560's timer4 and my initial results were not as expected. The results were
values of less than a byte (160 -240).

My next test I measured the duration of rotation of the ignition test stand at
lowest speed, ~26ms.

26ms => .026ms.
.026/.0000000625ns = 416,000 clock cycles.
416,000 x 64 prescaler = 6,500 counts @ ICRx register.

So, using the code below, I expected 6500 from ICR4, but I recorded ~160 to 240.

?? Does the Serial Library effect the entire program, and not just timer1??

?? I thought timer4 was independent of the program flow ??

Thanks

Bill M.

/* Basic_Input_Capture_Mega2560_v1.4
 *  
 * 0). Using Mega2560 timer4 because it is not effected by (???)
 * Arduino Serial library's use of timer1. ** But I do not know
 * id there is a Timer1 interrupt priority greater than timer4? 
 * 
 * 1). Confirmed that basic features of Timer#4 ICP does work. 
 */

volatile uint16_t period;


void setup() 
{
  pinMode(49,INPUT_PULLUP);          // ICP4 pin for Mega2560 Timer4.
  Serial.begin(9600);                // Serial Library uses Timer1
  
  TCNT4 = 0;                        // just trying to clear registers, I don't know
  TIMSK4 = 0;                       // if it is necessary.
  TCCR4A = 0;
  TCCR4B = 0;  
  

  TIMSK4 = (1<<ICIE4);              // Timer/Counter_n Input Capture interrupt is enabled
  
  TCCR4B = (1<<ICNC4);               //  Noise Canceler enabled
  
  TCCR4B &=~ (1<<ICES4);            // Falling edge select enabled
  
//  TCCR4B = (1<<CS42);               // Prescaler n/a
  TCCR4B = (1<<CS41);               // Prescaler for 64x prescaler 
  TCCR4B = (1<<CS40);               // Prescaler for 64x prescaler 
  
  
  
  sei();                            // Must be set to activate TIMSK4
}

void loop()
{
  Serial.println(period);              // print input capture clock cycles @ 64x prescaller
    period = 0;
}


ISR(TIMER4_CAPT_vect)
{      
   period = ICR4;                 // Input capture value. 
   
}