Theoretical max RPM tachometer

Hi!
I have built a tachometer using an IR led and receiver. The reflection of the LED on whatever is being measured triggers an interrupt via the IR receiver. I am pretty sure it works accurately, but my question is to what rpm (or interupt per second) I can expect reliable readings?

This is my code:

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR    0x27 // <<----- Add your address here.  Find it from I2C Scanner
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

unsigned int passes = 0;
unsigned long first = 0;
unsigned long last = 0;
unsigned long updateLCD = 0;

float numBlades = 2.0;

void blade_Pass(){
  passes++;
  if (passes == 1){
    first = millis();
  }
  else if (passes >= 10){
    last = millis();
  }
}

void setup(){
  Serial.begin(115200);
  lcd.begin (16,2);
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.home ();
  lcd.clear();
  //lcd.print("Hello!");
  
  attachInterrupt(0,blade_Pass,FALLING);
  
}


void loop(){
  if (passes >= 10){
    float passesPerSecond = (float)passes / ((float)last - (float)first) * 1000.0;
    float RPM = passesPerSecond * 60.0 / numBlades;
    Serial.print("Passes: ");
    Serial.print(passes);
    Serial.print(" Time: ");
    Serial.print(last-first);
    Serial.print(" PPS: ");
    Serial.print(passesPerSecond);
    Serial.print(" RPM: ");
    Serial.println(RPM);
    
    if (updateLCD < millis()){
      lcd.clear();
      lcd.home();
      lcd.print("RPM:");
      lcd.setCursor(5,0);
      lcd.print(RPM);
      updateLCD = millis() + 500;
    }
    
    passes = 0;
    delay(100);
  }
}
float numBlades = 2.0;

You might change this in the future to have 3.14159 blades?

passes is used in the ISR and in loop(), but is not volatile. Why not?

What happens, in the ISR, when passes gets to 11, 12, 13, etc.?

With your code the maximum is probably around 60000rpm. If you change from millis() to micros() you could reach much higher frequencies.

num blades is a float because I want rpm not to be rounded like when dividing by an integer. This way I don't have to do any convert it.
What's the difference with using a volatile (sorry if it's a dumb question)?

In the ISR I count the number of times something passes infront if the sensor, and records the time from the first pass to the most recent pass.
When I measure the RPM of a propeller spinning at rather high RPMs I generally reach 20-30 passes before the loop() has had time to process it.
Since loop() calculates the RPM by "passes / time" and time is always the timespan from pass one to the most recent I don't see this as a problem at all.

Thank you for your input to improve my code, but it is working I was just wondering to which RPM I can expect it to continue working.

Thank you for your reply Pylon. Would that be 60000 RPM witha 2-bladed prop or 60000 "passes of something" per minute infront of the sensor (i.e. 30000RPM for a 4-bladed prop)?

num blades is a float because I want rpm not to be rounded like when dividing by an integer. This way I don't have to do any convert it.

It doesn't need to be.

    float RPM = passesPerSecond * 60.0 / numBlades;

Divide a float by an int, and you still get a float.

What's the difference with using a volatile (sorry if it's a dumb question)?

The volatile declaration tells the compiler that the value can change at any time. Otherwise, in loop(), the value may be cached, and changes to the variable won't be seen.

In the ISR I count the number of times something passes infront if the sensor, and records the time from the first pass to the most recent pass.

Only if the "most recent pass" is pass number 10 or more. At very slow speeds, your RPM values will be wrong.

The whole idea of recording time only after 10 passes is flawed. Record the time EVERY time.

Thank you for your input to improve my code, but it is working

For some definition of working, I guess.

by measuring the time of 10 passes instead of 1 I get a more stable reading, with the average speed over 10 passes instead of every pass.

How will it be wrong if I am at less than ten? If you've missed it, I don't calculate if I havent got 10 or more passes, so a calculation with 5 passes and a old time would never occur.

Would that be 60000 RPM witha 2-bladed prop or 60000 "passes of something" per minute infront of the sensor (i.e. 30000RPM for a 4-bladed prop)?

Sorry, I missed the blades calculation, my calculation was with the passes. So you get up to approx. 60000 passes per minute but at that level you loose a lot of precision with that code although that's completely unnecessary.

Do you have any suggestions as to how I could improve my code? Although I doubt I'll need to measure anything above 40k, always like improvements.

Do you have any suggestions as to how I could improve my code?

As I said: use micros() instead of millis(), then you could calculate the RPM at each pass because you have an increased precision by about a factor of 100. You also don't have to count the passes, just store the difference between the last two interrupts in microseconds and do the calculation in the loop when you request the RPM value. This way you keep the ISR short and fast but keep a high precision. If you also want a high accuracy, use an Arduino with a crystal, not a resonator.