Arduino Tacho

Hi All,
I'm trying to construct an accurate tacho to check the RPM of shafts up to a maximum of 10,000 RPM. I have built the circuit with an arduino uno using both the serial and an LCD output. I am using a reflective Infra Red sensor as an input. However the readings are inconsistent and I have no faith in their accuracy. So, I am wondering, is the IR sensor the best input for this type of use or is there a more accurate way of sensing RPM?

The ir sensor should be capable of working, if your code and circuit are good. Did you paint the shaft with a spot of something that the ir sensor can reliably detect? Post your schematic and code. Read the forum guidelines in the sticky post first.

IR is also part of the ambient light, IR controls etc. Such circuits should be covered from other light sources.

What specific sensor are you using? (model number, manufacturer, etc)

Hi All again,
Sorry for the delay, been a busy few days. The IR sensor is one of the reflective typesconsisting of an IR emitter and a receiver LED, apparently they are used on the robot projects to detect white lines for guidance. I have tried reflective tape and white paint on the rotating motor disc and have also anchored both the motor and sensor, but the readout on the serial monitor is all over the place. It does stabilise when I adjust the distance pot on the sensor but even the stable readings are suspect. The motor plate says it gives 1400 RPM but I am getting readings of anything between 300 ish and 1600 ish. I am a novice with the code so I will try to post it here. The circuit layout is very simple: 5V and Ground to the sensor, with the output going to digital pin 2. The Arduino is an UNO R3.

[code]



const int dataIN = 2; //IR sensor INPUT

unsigned long prevmillis; // To store time
unsigned long duration; // To store time difference
unsigned long lcdrefresh; // To store time for lcd to refresh

int rpm; // RPM value

boolean currentstate; // Current state of IR input scan
boolean prevstate; // State of IR sensor in previous scan

void setup()
{
  pinMode(dataIN,INPUT);
       
  prevmillis = 0;
  prevstate = LOW;
  Serial.begin(9600);  
}

void loop()
{
 // RPM Measurement
  currentstate = digitalRead(dataIN); // Read IR sensor state
 if( prevstate != currentstate) // If there is change in input
   {
     if( currentstate == HIGH ) // If input only changes from LOW to HIGH
       {
         duration = ( micros() - prevmillis ); // Time difference between revolution in microsecond
         rpm = (60000000/duration); // rpm = (1/ time millis)*1000*1000*60;
         prevmillis = micros(); // store time for nect revolution calculation
       }
   }
  prevstate = currentstate; // store this scan (prev scan) data for next scan
  
  if( ( millis()-lcdrefresh ) >= 100 )
    {
      
      Serial.println(rpm);
    } 
    }

[/code]

F797KO8IQ8C99Y5.LARGE.jpg

I think I would get the input to generate an interrupt and from that count the transistions over a longish period of time ( 1second?) using Millis . By just reading the input there is a good chance you will miss the passing of your signal, especially at higher rpm .

I would not get into the complexities of interrupts for the moment. Let's see if we can do without those first.

Your 1400rpm is around 23 revolutions per second. But you are sending data to serial monitor 10 times per second, at only 9,600 baud, and that will be slow and interfere badly with timing the pulses.

So try updating serial monitor only after sampling the sensor for exactly 1000ms. During each 1000ms, count the number of detections from the sensor. It should be around 23. Multiply this by 60 to get your rpm.

Hi all again,
Many thanks PaulRB for your suggestions. I tried adjusting the baud rates but that did not solve the problem. I next tried to find out how to change the sensor sampling period as you suggested. However, with my coding ability (or rather the lack of it), I had no clue how to do this. Your remarks on the detection of around 23 revs / sec however, did give me some help. I eventually found a sketch on the Arduino site which counted button presses in a 5 sec. period. I changed the button switch input to accept an input from the IR reflective sensor I was using and found that it did in fact count 121 revs in the 5 sec. period (24.2 revs / sec). I then managed to put in some code to convert this to RPM. All this was working well but sending to the serial monitor. I next found a sketch which printed out to an LCD and managed to merge that part of it into my sketch and, after much “mackling” around – it worked! Of course the snag is that I still do not fully understand the code. However, it is working and I thank you again for your interest. I am sending a copy of the code and would be grateful of any comments regarding it. Thanks again.

//counts the revs in a 5 second period and displays the RPM on an LCD.
#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,4,5,6,7);   //defining lcd pins

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 5000;  //period during which button input  is valid
const byte IRpin = 2;    //IR sensor on pin 2
byte currentIRpinState;
byte previousIRpinState;
int count = 0;
boolean counting;
unsigned long rpm;
unsigned long lcdrefresh;

void setup()
{
  lcd.begin(16,2);
  pinMode(IRpin, INPUT);  
  counting = true;    //turn on the counting indicator
  startMillis = millis();  //save the start time
}

void loop()
{
  currentMillis = millis();
  previousIRpinState = currentIRpinState;
  if (currentMillis - startMillis <= period)  //true until the period elapses.  Note that this is the reverse of BWOD
  {
    currentIRpinState = digitalRead(IRpin);
    if (currentIRpinState == LOW and previousIRpinState == HIGH)  //the button has become pressed
    {
      count++;    //increment count
     rpm = (count/5*60);       
        
    }
  }
  else  //period has ended
  {
    if (counting == true)  //if we were counting
    {
    lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("RPM = ");
      lcd.print(rpm);
      lcd.setCursor(0,1);      
      lcd.print("Press reset");         
      lcdrefresh = millis();  
      
      counting = false;    //prevent the message being displayed again
    }
  }
}

Good work!

I've simplified and hopefully improved it a little. Please test this, I can't.

//counts the revs in a 5 second period and displays the RPM on an LCD.
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //defining lcd pins

unsigned long startMillis;
const unsigned long period = 5000UL;  //period during which button input  is valid
const byte IRpin = 2;    //IR sensor on pin 2
byte previousIRpinState;
int count = 0;

void setup()
{
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");

  pinMode(IRpin, INPUT);
  startMillis = millis();  //save the start time
}

void loop()
{
  unsigned long currentMillis = millis();

  if (currentMillis - startMillis <= period)  //true until the period elapses.  Note that this is the reverse of BWOD
  {
    byte currentIRpinState = digitalRead(IRpin);
    if (currentIRpinState == LOW and previousIRpinState == HIGH)  //the button has become pressed
    {
      count++;    //increment count
    }
    previousIRpinState = currentIRpinState;
  }
  else  //period has ended
  {
    int rpm = (60000UL * count / period);
    
    char str[6];
    sprintf(str,"%5d", rpm); 
    lcd.setCursor(0, 6);
    lcd.print(str);
    
    startMillis = millis(); //save the start time
  }
}

Does it still work OK?

Are there parts you need explaining?

Do you need more precision for the rpm figure and more frequent lcd updates? If so, you will find that reducing the value of period also reduces the precision. But there are ways to achieve both, if that's of interest.

Updating every 5 seconds means that one more or one less count in the period makes a difference if + or - 12rpm, so your precision is 12rpm. If you reduce the period to 1s, the precision drops to 60rpm.

To achieve high precision and frequent updates, we will need to accurately time, say, 10 counts. The timing will have to start at the instant when a pulse is received, and stop at the instant the 10th pulse is received. This would allow lcd updates around every 1s (at 1400rpm) and a precision of around 1rpm. If the timing was done in microseconds instead of milliseconds, the rpm could be calculated to a very high precision.

The code would then look something like this:

//counts the revs in a 5 second period and displays the RPM on an LCD.
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //defining lcd pins

unsigned long startMillis;
const int maxCount = 10;
const byte IRpin = 2;    //IR sensor on pin 2
byte previousIRpinState;
int count = 0;

void setup()
{
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");

  pinMode(IRpin, INPUT);
}

void loop()
{
  unsigned long currentMillis = millis();

  if (count <= maxCount)  //true until the required number of counts has been made
  {
    byte currentIRpinState = digitalRead(IRpin);
    if (currentIRpinState == LOW and previousIRpinState == HIGH)  //the button has become pressed
    {
      count++;    //increment count
      if (count == 1) //if this is the first pulse received, record the time
      {
        startMillis = currentMillis;
      }
    }
    previousIRpinState = currentIRpinState;
  }
  else  //max count has been exceeded, update the lcd
  {
    unsigned long period = currentMillis - startMillis;
    
    int rpm = (60000UL * maxCount / period);
    
    char str[6];
    printf(str,"%5d", rpm); 
    lcd.setCursor(0, 6);
    lcd.print(str);

    count = 0; // reset the count for the next update
  }
}

Again, I can't test it, so let me know how it goes.

Blimey that was quick. You're obviously well into this coding. I have copied the sketch into the IDE and will look at it as I get the time. My old brain does not function very fast, so don't hold your breath. Thanks again for your input.

Hi PaulRB,
Your sketch verified and uploaded OK. However the LCD is not showing any readings. The first row has the “RPM =” but the second row has only 4 horizontal bars in the column 1 position. The bars are not shown when the sketch starts or is reset, only when the sensor has sensed a revolution or 2 of the motor. With the motor running, if the reset is pressed the LCD briefly lights up several segments on the second row, no figures, just the segments lit up briefly. I am not screaming for help yet! I have copied 2 copies of your sketch into the Arduino IDE and am playing around with one of them. I would like to work it out for myself if I can but please don’t go away. :slight_smile: Many thanks – again.

My bad. The error is on this line

    lcd.setCursor(0, 6);

As you are so keen, I'll let you work out what my mistake was.

Which of the two sketches i posted are you testing?

PS. This type of LCD display uses 6x7 grids of dots to make each character. There are no "segments" as you would see on a 7-segment display.

when I copied your sketches and compared them they were both the same so I assumed you had posted one sketch twice by mistake. Having looked again I realise that I am going blind or I am thicker than I thought I was but have put it down to old age! I now have the 2 different copies so it's back to the drawing board. The LCD I am using has 2 rows of 16 "segments" (sorry if that's a bad word to describe them). Thinking about that has given me a clue to your "deliberate mistake", thanks for the clue. I will be back on the scent again as soon as I get time. Thanks again.

Malc92:
The LCD I am using has 2 rows of 16 "segments" (sorry if that's a bad word to describe them).

"Characters" is the correct term. Each character is displayed on a matrix of 5x7 dots.

Hi PaulRB,
I am still here but struggling now. I am still working on the second sketch you posted. Looking at the lcd.setCursor(0,6), after some reading up I realised that the numbers in the brackets were the wrong way round (y - x instead of x- y) as the second number should have been either a 0 or 1 for a 16,2 LCD, I thought I had cracked it. I changed the line to lcd.setCursor(0,1), this moved the first character to the first column on the second row and this character lights up with 4 horizontal bars when the sensor senses the motor rotating. However no reading is displayed, just several whole characters to the right lighting up briefly after the reset is pressed. I then wondered whether the lcd.setCursor(0,1) line was in the wrong place and tried to work out where else the LCD cursor should be reset, so I tried changing the position of the line in the sketch, but with my poor understanding of code this was all to no avail. So, perhaps you could give me another clue – please? Thanks in anticipation!

Malc92:
Looking at the lcd.setCursor(0,6), after some reading up I realised that the numbers in the brackets were the wrong way round (y - x instead of x- y)

Correct, that was my error. Or at least I thought it was...

Malc92:
However no reading is displayed, just several whole characters to the right lighting up briefly after the reset is pressed.

I will review again and see if I can spot another error.

Hi Paul,
Just to keep you posted. While you were busy I thought I would have another play with the first sketch you posted. The first suprise was that despite the lcd.setCursor (0,6) the LCD was actually positioning the first character at (4,1) (5th column, 2nd row). I changed the statement to lcd.setCursor (6,0) to move the first character on to the first row expecting the first character to appear immediately after the ” RPM = “ when it appeared one character to the right I realised (assumed) that your sprint(str,%5d,rpm); line was allowing for 5 figures to be returned?? Not to be put off by these minor queries I pressed on.

The real problem was that the LCD readout changed every 5 secs. It kept showing a higher RPM. I then realised that the new readings were all about 1400 greater than the previous ones, it was adding the new readings to the previous readings. To stop this I entered count = false; at the end of the last loop in the sketch. Lo and behold I now have the sketch working, as you say it can vary by about 12 RPM which is not terrible but, being a fussy old fart I would be interested in improving the accuracy.

   char str[6];
    sprintf(str,"%5d", rpm); 
    lcd.setCursor(6,0);
    lcd.print(str);
    
    startMillis = millis(); //save the start time
    count = false;
  }
}

Malc92:
I realised (assumed) that your sprint(str,%5d,rpm); line was allowing for 5 figures to be returned??

Yes, 5 figures, because you said:

Malc92:
I'm trying to construct an accurate tacho to check the RPM of shafts up to a maximum of 10,000 RPM.

That's what the "%5d" format means: display a decimal number, padded with leading spaces to make it width 5 characters. If you try changing it to "%05d" it will pad with leading zeroes.

Malc92:
it was adding the new readings to the previous readings. To stop this I entered count = false;

Well spotted! Your fix is almost correct, it should be "count = 0;" because count is an int, not a boolean. C language is not very strict about assigning values of different types, so the compiler did not complain, and it so happens that "false" is actually represented internally as zero. So by a small degree of luck, your fix worked.

What about the second sketch I posted? You will need to fix the setCursor in that one also. At least in that version, I remembered to set "count = 0;"

Hi Paul
I am still struggling with the second sketch, still the same problem I had on June 30 posting:

The bars are not shown when the sketch starts or is reset, only when the sensor has sensed a revolution or 2 of the motor. With the motor running, if the reset is pressed the LCD briefly lights up several segments on the second row, no figures, just the segments lit up briefly
Looking back at your comments on the second sketch in your June 29 posting:
“To achieve high precision and frequent updates, we will need to accurately time, say, 10 counts. The timing will have to start at the instant when a pulse is received, and stop at the instant the 10th pulse is received. This would allow lcd updates around every 1s (at 1400rpm) and a precision of around 1rpm. If the timing was done in microseconds instead of milliseconds, the rpm could be calculated to a very high precision[[/i.”
If 1400RPM is about 23 revs / sec. then a count of 10 revs will take 10/23 = 0.434 sec., meaning that the LCD will update about twice every second. My maths leave a lot to be desired so please feel free to correct me if this is wrong.
In an effort to understand the sketch I have been through it and changed comments to what I think is happening. I have tried changing several parts of the sketch but nothing seems to get me any further than the June 30th situation. I am posting the code and would be grateful for your comments. Thanks again for you time.
```
*//counts time elapsed during 10 pulses and displays as RPM on an LCD.
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //defining lcd pins

unsigned long startMillis;
const int maxCount = 10;
const int IRpin = 2;    //IR sensor on pin 2
int previousIRpinState;
int count = 0;

void setup()
{
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");
  pinMode(IRpin, INPUT);
}

void loop()
{
  unsigned long currentMillis = millis();

if (count <= maxCount)  //true until the required number of counts has been made
  {
    int currentIRpinState = digitalRead(IRpin); //state of IR pin is whatever pin 2 is, (High or low)
    if (currentIRpinState == LOW and previousIRpinState == HIGH)  //a pulse has been received
    {
      count++;    //increment count
      if (count == 1) //if this is the first pulse received, record the time
      {
        startMillis = currentMillis;  //reset timer to start at first pulse
      }
    }
    previousIRpinState = currentIRpinState;  //reset IR pin to its previous state
  }
  else  //max count has been exceeded, 10 pulses have been counted, update the lcd
  {
    unsigned long period = currentMillis - startMillis;  //calculate the length of the period

int rpm = (60000UL * period / maxCount);  //if the period is time taken for 10 counts then, time taken for 1 count = period / maxcount ??

char str[6];
    printf(str, "%5d", rpm);
    lcd.setCursor(0, 1);
    lcd.print(str);

count = 0; // reset the count ready for the next first pulse

}
}*
```

Try this

//counts time elapsed during 10 pulses and displays as RPM on an LCD.
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //defining lcd pins

unsigned long startMillis;
const int maxCount = 10;
const int IRpin = 2;    //IR sensor on pin 2
int previousIRpinState;
int count = 0;


void setup()
{
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");
  pinMode(IRpin, INPUT);
}

void loop()
{
  unsigned long currentMillis = millis();

  int currentIRpinState = digitalRead(IRpin); //state of IR pin is whatever pin 2 is, (High or low)
  if (currentIRpinState == LOW and previousIRpinState == HIGH)  //a pulse has been received
  {
    count++;    //increment count
    if (count == 1) //if this is the first pulse received, record the time
    {
      startMillis = currentMillis;  //reset timer to start at first pulse
    }
    else if (count > maxCount)  //true when the required number of counts has been made
    {
      unsigned long period = currentMillis - startMillis;  //calculate the length of the period

      int rpm = (60000UL * period / maxCount);  //if the period is time taken for 10 counts then, time taken for 1 count = period / maxcount ??

      char str[6];
      printf(str, "%5d", rpm);
      lcd.setCursor(0, 1);
      lcd.print(str);

      count = 0; // reset the count ready for the next first pulse
    }
  }
  previousIRpinState = currentIRpinState;
}