tachometer, review of methods

i' m trying to implement a tachometer in a project, my need is to measure the rpm of a brushless motor with not more than 4000rpm at max speed, usually.

right now i have been passed two days in front of the computer reading a lot of topic, instructables and other stuff. some of them:
1, 2, 3, 4, 5

(you don t need to read everything, don t worry, i m going to do a little review)

so there are two main methods: using an ir photoresistor or using a hall sensor

then there are three main approaches in coding: pulseIn, interrupts, and if statements

so my question is: is here someone who knows which is the best/simplest way? or have in general any suggestion, idea?
right now i have tried using ir led with interrupts and if statements but any help would be very appreciate
something that could be usefull that i read today is to use micros() insted of millis() to have a better resolution

I have not read your links, but I think when you say "if statements", you are referring to the technique known as "polling" where the program checks an input over and over on a regular basis.

As to which is best of the 3 techniques, it depends. The "pulse in" method is probably the easiest to code and the "interrupt" technique is most advanced. But even an experienced programmer chooses the easiest technique unless there is a good reason to choose a more complex one.

So tell us more about the overall project, how you will design the sensor, what else the Arduino will do and so on.

I am building a complete thrust station
I will have temperature sensors for esc and motor
Thrust of the motor with load cell and hex711
Pwm input for the esc
Multimeter (Current, voltage and watt)
Lcd and a serial port for data logging with processing
And finally (where i m stuck) a tachometer.
Everything with a nano

The code will be structured to many single function in the loop like as getRPM(); getTemperature(); ecc

Right now i have used tcrt5000 with lm393 (it is a breakboard) i don t want to use the hall effect i would rather achieve it by ir

I didn t been lucky with the polling but i have for sure made some mistake and anyway i think it will be too slow

Your first post made sense. Your second post did not. Please explain everything as though you are speaking to someone technically minded but not expert in your interests. Start by explaining what a thrust station does and then the other terms you have used and finally explain what it is that you think will be too slow and why.

Right now i have used tcrt5000 with lm393 (it is a breakboard) i don t want to use the hall effect i would rather achieve it by ir

If you are getting a clean digital output from your breadboard to the interrupt pin, there is no need to change the sensor from reflected ir to Hall.

Using interrupts is very appropriate in this application.

Write some simple test code to just determine rpm and let us know how it is working.

a thrust station is something like this:

basically it will just calculate the thrust of a motor using a load cell, the motor is controlled by an ESC (electronic speed control) which works sending to the motor a PWM signal. i would like do to some implementation:
firstly i want to control the PWM signal and for this i will use a potentiometer and the library servo.h
then i want some temperatures and i will use the dallas sensors
for the voltage i will use a divider and for the current the ACS712

so returning to the main topic: the tachometer.

finally explain what it is that you think will be too slow and why.

about this i was talking about mine (probably) wrong polling code but as cattleddog said:

Using interrupts is very appropriate in this application.

since i m not expert like you i will follow your suggestions so if you say that interrup are better i will work on them

my code is a copy of an example i get from the arduino playground and that i found in other places

so it is an interrupt that acts everytime something pass in front of the ir, the interrept is very simple, it just increase a value, later in the loop the interrup is disabled to not interfere with the calculation of the rpm

volatile byte half_revolutions;

 unsigned int rpm;

 unsigned long timeold;

 void setup()
 {
   Serial.begin(9600);
   attachInterrupt(0, rpm_fun, RISING);

   half_revolutions = 0;
   rpm = 0;
   timeold = 0;
 }

 void loop()
 {
   if (half_revolutions >= 20) { 
     //Update RPM every 20 counts, increase this for better RPM resolution,
     //decrease for faster update
     rpm = 30*1000/(millis() - timeold)*half_revolutions;
     timeold = millis();
     half_revolutions = 0;
     Serial.println(rpm,DEC);
   }
 }

 void rpm_fun()
 {
   half_revolutions++;
   //Each rotation, this interrupt function is run twice
 }

now, before i thought that my problem was that if a single blade of the propeller is passing only once in front of the ir but not enough fast i get a sensor read like this:

0
0
1
1
1
0
0
0

where 1 it is when it is over the ir sensor, so the function think that the blade passed 3 times but really it passed only once, but thinking more on it it is not a problem because the interrup acts only when the state of the pin change, so for now i m with no clue

these are some tipical rpm values that i get passing a finger at different fast in front of the sensor:

150, 180, 3230, 2790, 460 and so on

Right now i have used tcrt5000 with lm393 (it is a breakboard) i don t want to use the hall effect i would rather achieve it by ir

I'm quite sure that these boards would produce some false pulses for a slow changing input. The problem is that the circuit for the comparator doesn't have any feedback for hysteresis. You could resolve this by providing debounce in your ISR code.

Here is some very simple pulse counting code which has the proper handling of the transfer of data from the interrupt routine. It counts for one second, so rpm will be 30x the count with a two bladed propellor. With the lm393 boards, the most simple solution is to add a capacitor(100nf) between the digitalout/interrupt pin and ground. See this thread FC-03 and attachInterrupt() problems!?!? - Sensors - Arduino Forum

volatile unsigned long  count = 0;
unsigned long copyCount = 0;

unsigned long lastRead = 0;
unsigned long interval = 1000;//one second

void setup()
{
  Serial.begin(115200);
  Serial.println("start...");
  
  pinMode(2,INPUT_PULLUP);
  attachInterrupt(0, isrCount, RISING); //interrupt signal to pin 2
}

void loop()
{
  if (millis() - lastRead >= interval) //read interrupt count every second
  {
    lastRead  += interval;
    // disable interrupts,make copy of count,reenable interrupts
    noInterrupts();
    copyCount = count;
    count = 0;
    interrupts();
 
    Serial.println(copyCount);

  }
}

void isrCount()
{
  count++;
}

Can you provide a schematic of your tcrt5000/lm393 breakout board?
If the simple capacitor doesn't work, we can provide debounced interrupt code, but try hardware first.

aster94:
i' m trying to implement a tachometer in a project, my need is to measure the rpm of a brushless motor with not more than 4000rpm at max speed, usually.

4000rpm = 66 2/3 rps = 15 millis/rev = 41 2/3 micros per degree to put it on Arduino-speed-scales.

If you can put a magnet on the shaft and use 2 hall switches, the time from one to the next gives you inverse speed and you also get direction of turn.

Make instead a ring of 6 sensors (on Uno, 8 on Nano, Mega, some others) around the shaft all wired to the pins on one port. Then direct port read the sensors (1 cycle) and operate on all of the sensors as bits in 1 byte, use logic operations to work them all at once and not in some baroque loop. The only non-zero will be a single bit, progress should be easy to track. The Mega is full-port rich and has 8K RAM and 4 serial ports.

6 sensors, 60 degrees apart, 2500 micros, 2.5 millis apart at 4000 rpm.

Set a Pin Change Interrupt on all of the pins and capture the port in the IRQ or poll at 50KHz?

With an Uno you can handle 3 sets of 6 switches max, Uno has no port with more than 6 pins free.

i have added some serial print to show you the behaviour of the sensors:

volatile byte rpmcount;
unsigned int rpm;
unsigned long timeold;

void setup() {

  Serial.begin(115200);

  rpmcount = 0;
  rpm = 0;
  timeold = 0;

}

void loop()
{

  if (rpmcount >50){
  detachInterrupt(digitalPinToInterrupt(3));
  rpm = 30 * 1000 / (millis() - timeold) * rpmcount;
  timeold = millis();
  rpmcount = 0;  
  }
  attachInterrupt(digitalPinToInterrupt(3), rpm_fun, FALLING);
  Serial.print("rpmcount:  ");
  Serial.print(rpmcount);
  Serial.print("  |  sensor:  ");
  Serial.print(digitalRead(3));
  Serial.print("  |  rpm:  ");
  Serial.println(rpm);
}

void rpm_fun() {
  rpmcount++;
}

i have two ir sensors

ir-08h that i am not using


because it has mounted a HC00AG and on page 4 fig 1 shows that has a "time delay" so i thought it was less correct to use it

and then the ir sensor that i am using is this:

i wasn t able to find any shematics... but here there aren t any capacitator, the potentiometer is used to adjust the sensibility distance

so the output of this sensor is this, i am coping only relevant parts:

rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  9  |  sensor:  0  |  rpm:  0
rpmcount:  9  |  sensor:  0  |  rpm:  0
rpmcount:  9  |  sensor:  0  |  rpm:  0

rpmcount:  9  |  sensor:  0  |  rpm:  0
rpmcount:  9  |  sensor:  1  |  rpm:  0
rpmcount:  16  |  sensor:  1  |  rpm:  0
rpmcount:  16  |  sensor:  1  |  rpm:  0

rpmcount:  16  |  sensor:  1  |  rpm:  0
rpmcount:  16  |  sensor:  1  |  rpm:  0
rpmcount:  16  |  sensor:  1  |  rpm:  0
rpmcount:  22  |  sensor:  0  |  rpm:  0
rpmcount:  22  |  sensor:  0  |  rpm:  0
rpmcount:  22  |  sensor:  0  |  rpm:  0
rpmcount:  22  |  sensor:  0  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0


rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  30  |  sensor:  1  |  rpm:  0
rpmcount:  43  |  sensor:  0  |  rpm:  0
rpmcount:  43  |  sensor:  0  |  rpm:  0
rpmcount:  43  |  sensor:  0  |  rpm:  0
rpmcount:  43  |  sensor:  0  |  rpm:  0
rpmcount:  43  |  sensor:  0  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  364
rpmcount:  0  |  sensor:  1  |  rpm:  364
rpmcount:  0  |  sensor:  1  |  rpm:  364
rpmcount:  0  |  sensor:  1  |  rpm:  364


rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  24  |  sensor:  0  |  rpm:  0
rpmcount:  24  |  sensor:  0  |  rpm:  0
rpmcount:  24  |  sensor:  0  |  rpm:  0
rpmcount:  24  |  sensor:  0  |  rpm:  0
rpmcount:  24  |  sensor:  1  |  rpm:  0
rpmcount:  47  |  sensor:  1  |  rpm:  0
rpmcount:  47  |  sensor:  1  |  rpm:  0
rpmcount:  47  |  sensor:  1  |  rpm:  0


rpmcount:  47  |  sensor:  1  |  rpm:  0
rpmcount:  47  |  sensor:  1  |  rpm:  0
rpmcount:  47  |  sensor:  1  |  rpm:  0
rpmcount:  58  |  sensor:  0  |  rpm:  0
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  0  |  sensor:  0  |  rpm:  754
rpmcount:  11  |  sensor:  1  |  rpm:  754
rpmcount:  11  |  sensor:  1  |  rpm:  754
rpmcount:  11  |  sensor:  1  |  rpm:  754

now i am going to try your suggestions

P.S: i would rather do ir with ir sensor not hall effect

EDIT:

i didn t try with the debouncing in the isr because i like to take it "as short as possible"

so i used the capacitator:

rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  1  |  rpm:  0
rpmcount:  0  |  sensor:  0  |  rpm:  0
rpmcount:  1  |  sensor:  0  |  rpm:  0
rpmcount:  1  |  sensor:  0  |  rpm:  0
rpmcount:  1  |  sensor:  0  |  rpm:  0
rpmcount:  1  |  sensor:  0  |  rpm:  0
rpmcount:  1  |  sensor:  1  |  rpm:  0
rpmcount:  1  |  sensor:  1  |  rpm:  0
rpmcount:  1  |  sensor:  1  |  rpm:  0

pmcount:  1  |  sensor:  1  |  rpm:  0
rpmcount:  1  |  sensor:  1  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  2  |  sensor:  1  |  rpm:  0
rpmcount:  2  |  sensor:  1  |  rpm:  0
rpmcount:  2  |  sensor:  1  |  rpm:  0

rpmcount:  2  |  sensor:  1  |  rpm:  0
rpmcount:  2  |  sensor:  1  |  rpm:  0
rpmcount:  2  |  sensor:  1  |  rpm:  0
rpmcount:  2  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  0  |  rpm:  0
rpmcount:  3  |  sensor:  1  |  rpm:  0
rpmcount:  3  |  sensor:  1  |  rpm:  0
rpmcount:  3  |  sensor:  1  |  rpm:  0

rpmcount:  13  |  sensor:  1  |  rpm:  0
rpmcount:  13  |  sensor:  1  |  rpm:  0
rpmcount:  13  |  sensor:  1  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  0  |  rpm:  0
rpmcount:  14  |  sensor:  1  |  rpm:  0
rpmcount:  14  |  sensor:  1  |  rpm:  0
rpmcount:  14  |  sensor:  1  |  rpm:  0

my thanks and complimets to everyone expecially cattledog
i tried it with a fan and looks good, in the next days i will try with the brushless motors hoping it will be enough fast for them

i also tried to put the INPUT_PULLUP in my interrept but it looks like that works worst (but i could be just a mine impression)

We debounce dirty signals like contact switches closing and opening (tiny sparks) which you won't get with reflected IR.

INPUT_PULLUP is ground-safe, low powered, and hold its state unterminated. But if you have it wired for some other use with a pulldown resistor after the switch then it's not going to work so well.

Try adding micros() to your print lines and shorten those labels to single-char to reduce print overhead.

We debounce dirty signals like contact switches closing and opening (tiny sparks) which you won’t get with reflected IR.

The OP’s problem is not due to the nature of the reflected IR but with the behavior of the lm 393 comparator ( used in the circuit without feedback )which is processing the signal from the IR receiver photo transistor.

The constant detachment and reattchment of the interrupt is not correct. Attach it only once in setup and use the noInterrupts() and interrupts() calls to pull unchanging data from the isr.

Here is a cleaner form of the code to read a period of time for a number of pulses.

volatile byte  count = 0;
byte numCount = 50;

volatile unsigned long startTime;
volatile unsigned long endTime;
unsigned long copy_startTime;
unsigned long copy_endTime;

volatile boolean finishCount = false;
float period;

unsigned int rpm = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println("start...");

  attachInterrupt(digitalPinToInterrupt(3), isrCount, FALLING);//interrupt on pin3
}

void loop()
{
  if (finishCount == true)
  {
    finishCount = false;//reset flag
    // disable interrupts, make protected copy of time values
    noInterrupts();
    copy_startTime = startTime;
    copy_endTime = endTime;
    interrupts();

    period = (copy_endTime - copy_startTime) / 1000.0;//micros to millis
    Serial.println(period);//debug
   

    rpm = numCount * 30.0 * (1000.0 / period);//two ccounts per revolution

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

void isrCount()
{
  count++;

  if (count == 1)//increments on first entry to isr
  {
    startTime = micros();
  }

  if (count == numCount)
  {
    endTime = micros();
    finishCount = true;
    count = 0;
  }
}

The OP's problem is not due to the nature of the reflected IR but with the behavior of the lm 393 comparator ( used in the circuit without feedback )which is processing the signal from the IR receiver photo transistor.

This is for a turning shaft with a dark or light spot to detect, as opposed to a magnet?

Why the comparator? Just use the detector as a switch to ground the pin or not. Choke the view with an aperture if need be.

At full speed this is the one that takes 41usecs to turn 1 degree? How wide is the mark? You should be able to catch that every time just polling the sensor with a 1 degree wide mark. Even port reading up to 8 sensors you shouldn't miss it passing any of them at least if you write clean non-blocking code.

Interrupts have an overhead that can trip you up. Attaching and detaching just makes it trickier.

cattledog:
The OP's problem is not due to the nature of the reflected IR but with the behavior of the lm 393 comparator ( used in the circuit without feedback )which is processing the signal from the IR receiver photo transistor.

The constant detachment and reattchment of the interrupt is not correct. Attach it only once in setup and use the noInterrupts() and interrupts() calls to pull unchanging data from the isr.

Here is a cleaner form of the code to read a period of time for a number of pulses.

volatile byte  count = 0;

byte numCount = 50;

volatile unsigned long startTime;
volatile unsigned long endTime;
unsigned long copy_startTime;
unsigned long copy_endTime;

volatile boolean finishCount = false;
float period;

unsigned int rpm = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println("start...");

attachInterrupt(digitalPinToInterrupt(3), isrCount, FALLING);//interrupt on pin3
}

void loop()
{
  if (finishCount == true)
  {
    finishCount = false;//reset flag
    // disable interrupts, make protected copy of time values
    noInterrupts();
    copy_startTime = startTime;
    copy_endTime = endTime;
    interrupts();

period = (copy_endTime - copy_startTime) / 1000.0;//micros to millis
    Serial.println(period);//debug

rpm = numCount * 30.0 * (1000.0 / period);//two ccounts per revolution

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

void isrCount()
{
  count++;

if (count == 1)//increments on first entry to isr
  {
    startTime = micros();
  }

if (count == numCount)
  {
    endTime = micros();
    finishCount = true;
    count = 0;
  }
}

Wow i love this code, i can t wait to go back home and try it. I liked that you used a fixed number of revolutions and the only parameter that changes is the time, simpler.

Do you think that without the lm393 i ll have a better read? It isn t a problem to take the tcrt5000 alone, in the next days i will look for a schematics for the resistors and then i will try

The blades of the propeller are 1 cm wide

aster94:
in the next days i will look for a schematics for the resistors and then i will try

The blades of the propeller are 1 cm wide

If you mode the pin INPUT_PULLUP then you can safely ground it through the phototransistor.
INPUT_PULLUP supplies the pin with 5V through a 20k to 50k internal resistor, the current is small.

i run some test with and without the lm393

using these two schematics

but it difficult to understand which one work better, maybe i should try to use a professional tachometer as reference

in your opinion theoretically lm393 yes or not?

but it difficult to understand which one work better, maybe i should try to use a professional tachometer as reference

in your opinion theoretically lm393 yes or not?

If both methods produce clean interrupt pulse it does not matter. The lm393 has the sensitivity adjustment in case the receiving photo transistor does not go into saturation but has the requirement for the capacitor on the output.

The signal roll off from the capacitor may limit the high rpm readings. If you can reliably get an interrupt signal without the lm395 and capacitor I’d go that route. If the IR reflectivity of the prop is not going to vary, and the sensor setup distance is constant you probably don’t need the flexibility provided by the lm393 board.

Ok thank you cattledog, yesterday i made a very tiny board with just the two ir led and a potentiometer. A friend of mine have a professional tachometer so when i ll be able to try it i will post the results of the three systems

I'm not sure how the potentiometer in the simple emitter/receiver is useful. A fixed 10K pullup on the collector should be fine.

If you had a receiver where you had access to the base of the ir photo transistor then you might be able to bias the sensitivity, but changing the value of the pullup resistor should not have that effect.