I'm trying to create a tacho for my lathe spindle, I'm using the the design/code from linky
The LCD will display text and if I amend the code to use the serial monitor on pin 2 I can see the IR logic changes state when something passes through the IR beam but the RPM on the LCD never displays anything other than 0.
I'm a complete noob so any assistance much appreciated
//DIY Tachometer to Measure Accurate RPM using Arduino
//DIY Tachometer to Measure Accurate RPM using Arduino
//This code is published by https://www.circuitschools.com
//Attribution required to republish.
#include <LiquidCrystal_I2C.h>
// Create the lcd object address 0x27(get it from i2cscanner) and 16 columns x 2 rows
LiquidCrystal_I2C lcd (0x27, 16,2); //
float value=0;
float rev=0;
int rpm;
int oldtime=0;
int newtime;
void isr() //interrupt service routine
{
rev++;
}
void setup()
{
lcd.init (); //initialize LCD
// Turn on the backlight on LCD.
lcd. backlight ();
attachInterrupt(digitalPinToInterrupt(2),isr,RISING); //attaching the interrupt
}
void loop()
{
delay(1000);
detachInterrupt(0); //detaches the interrupt
newtime=millis()-oldtime; //finds the time
int wings= 3; // no of wings of rotating object, for disc object use 1 with white tape on one side
int RPMnew = rev/wings; //here we used fan which has 3 wings
rpm=(RPMnew/newtime)*60000; //calculates rpm
oldtime=millis(); //saves the current time
rev=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("___TACHOMETER___");
lcd.setCursor(0,1);
lcd.print( rpm);
lcd.print(" RPM");
lcd.print(" ");
attachInterrupt(digitalPinToInterrupt(2),isr,RISING);
}
//float rev=0;
volatile unsigned int rev;
int rpm;
//int oldtime=0;
//int newtime;
unsigned long oldtime=0;
unsigned long newtime;
The calculation of rpm is wrong. RPMNew is really revolutions per second, and its a simple *60 to get rpm. I would rename RPMNew to RPSNew to avoid confusion.
int RPMnew = rev/wings; //here we used fan which has 3 wings
//rpm=(RPMnew/newtime)*60000; //calculates rpm
rpm = RPMnew*60;
Hi guys - thanks for your replies - it looks like this has fixed it. Though I'm now having some issues with noise from my VFD, causing erroneous RPMS! I'll see if I can get to the bottom of the noise and report back.
I had an almost identical project with almost identical issues.
I'm using an Hall sensor, not IR sensor.
It worked great measuring the rpm on my hand drill, a small motor an my bench and the spindle on my Haas VMC. However, when tested on my friend's Tormach mill, which is what I made it for, the rpm was all over the place. Major EMI problems.
The issue was the wires to attach the sensor. I'd just grabbed three randon wires.
I chopped up some shielded USB cable, added a small ferrite bead near the MCU end and also a 0.1uF bypass capacitor at the input of the signal.
Problem solved. Works perfectly even in my mate's shop that full of electrically noisy Tormach Mills and lathes.
Noise issues now resolved using the suggestion from Matt_TYGA (thanks) The RPM now remains at 0 while the moror is runnin and the spindle in 'neutral'.
The next issue is the RPM doesnt seem to be reading correctly. I'm testing using 4 bits of white tape on the spindle shaft to trigger the IR sensor which appeaers to work. When rotating the spindle the RPM figure seems to fluctuate and doesnt go more than a few hunder RPM.
The spindle speed ranges from 20-3000 RPM, I assume i'm introducing some error by not having all 4 pieces of tape exactly 90 degrees apart, but I dont understand why for eaxmple if I run at around 500 RPM the display is fluctuating at around 250 max?
Easies would be to use only 1 piece of tape, and get only 1 count per revolution.
Unless you are needing extremely precise RPM readings this should suffice for most applications.
As you found, VFDs are very noisy. And, unlike in many other motors, VFDs need to have the cable grounded both at the motor end and the VFD end.
I've made some progress but still not working 100%. At high speed the reading seems to be fairly stable fluctuating maybe <10%. At low speeds say <500 RPM the reading doesnt seem very stable atall and I don't think its reading correclty. (this is using 1 or 4 pieces of tape, 4 appears to be more stable)
by tweaking the pot on the IR sensor there is some 'calibration' that is required but even so I cant get it to read right. I assume the width of the tape isnt relevant due to the interrupt occuring on the rising edge of the pulse ?
I wouldn't expect the reading to be that erratic. It is only 8-9 Hz.
Could you please post your updated code and also a picture or two of your actual setup?
Optical interference a possibility?
Do you have an optical tach handy that you could use to confirm?
I have a cheap laser tach that is very useful. It comes with reflective tape. Here are my results:
Bandsaw, reflective tape on black painted cast iron wheel: 790+/- 0.4 rpm
Lathe 6" shiny chuck:
90 rpm setting -> 87
205 -> 203
505 -> 495
Here is the interesting part. If the tach was too close or laser spot was off by more than half the width of the tape, the readings were all over the place, ranging from 990 - 1184.
There is some calibration required with regards to lighting/ sensor distance and tweaking of the pot. I have looked at optical interference, the LED lights in my garage to seem to affect the readout but even with them off I cant seem to get the setup to work correctly.
I'm going to speak with a friend whom I think has a hand held tacho and hopefully get an ideal of if/when the tacho is reading correctly at higher RPM.
code below, I've experimented with 4 & 1 pieces of tape on the spindle with pretty much the same outcome.
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd (0x27, 16,2);
float value=0;
volatile unsigned int rev;
int rpm;
unsigned long oldtime=0;
unsigned long newtime;
void isr() //interrupt service routine
{
rev++;
}
void setup()
{
pinMode(13, OUTPUT);
lcd.init (); //initialize LCD
// Turn on the backlight on LCD.
lcd. backlight ();
attachInterrupt(digitalPinToInterrupt(2),isr,RISING); //attaching the interrupt
}
void loop()
{
delay(1000);
digitalWrite(13, HIGH);// power for IR sensor
detachInterrupt(0); //detaches the interrupt
newtime=millis()-oldtime; //finds the time
int wings= 4; // no of wings of rotating object, for disc object use 1 with white tape on one side
int RPMnew = rev/wings; //here we used fan which has 3 wings
rpm=RPMnew*60; //calculates rpm
oldtime=millis(); //saves the current time
rev=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("___TACHOMETER___");
lcd.setCursor(0,1);
lcd.print( rpm);
lcd.print(" RPM");
lcd.print(" ");
attachInterrupt(digitalPinToInterrupt(2),isr,RISING);
}
Pics of the setup below, I’ve tried the sensor two different ranges from the spindle with the same results. I’ve also put some tape around the sensors to try and narrow the beam angle.
(I’m only allowed to upload one pic as a new user apparently??)
I noticed your code is spending far too much time processing with interrupts detached. Try a method like this ...
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd (0x27, 16, 2);
float value = 0;
volatile unsigned int rpm, rev, revCopy;
unsigned long newtime, oldtime = 0;
void isr() //interrupt service routine
{
rev++;
}
void setup()
{
pinMode(13, OUTPUT);
lcd.init (); //initialize LCD
// Turn on the backlight on LCD.
lcd. backlight ();
attachInterrupt(digitalPinToInterrupt(2), isr, RISING); //attaching the interrupt
}
void loop()
{
delay(1000);
digitalWrite(13, HIGH);// power for IR sensor
cli(); //stop interrupts
revCopy = rev;
rev = 0;
sei(); //allow interrupts
newtime = millis() - oldtime; //finds the time
unsigned int wings = 4; // no of wings of rotating object, for disc object use 1 with white tape on one side
unsigned int RPMnew = revCopy / wings; //here we used fan which has 3 wings
rpm = RPMnew * 60; //calculates rpm
oldtime = millis(); //saves the current time
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("___TACHOMETER___");
lcd.setCursor(0, 1);
lcd.print( rpm);
lcd.print(" RPM");
lcd.print(" ");
}
Suggestions:
Wrap the spindle with matte black tape, then add the white tape. This would eliminate anything reflective in between the white strips and the higher contrast should improve the sensor's response.
You can get much better accuracy if you measure the elapsed microseconds between each interrupt. No need to count anything, just use a different formula for calculating RPM based on the pulse time period.
Nice set of gears . What lathe do you have? And I presume you are able to install the cover when running/testing the rpms?
Thanks for posting the code.
I have a few small suggestions:
move const int wings= 4; to above setup. It does not need to be re-initialised every time
Same with declaring int RPMnew = 0; so it does not need to be re-declared every time through loop
Move digitalWrite(13, HIGH); to setup. It only needs to be run once in your code
Next, I don't see where you are using the result of newTime.
newtime = millis() - oldtime; // May be better called elapsed time
rev = 0; // doing this and next line immediately will allow rev to accumulate while doing other stuff
oldtime = millis(); //saves the current time
RPMnew = 1000*(rev/wings)/newTime; //
rpm= RPMnew*60; //calculates rpm
thanks for all the replies. I'm a bit short on time this week but had a quick play in the garage this morning.
Still not luck, interestingly i put my scope on the IR sensor O/P and you can clearly see the O/P going LOW when a piece of tape passes. So I've amended the interrupt to trigger on the low. I think it's looks like the issue is purely a software one but I don't know why. To my untrained eye the code below looks like it should work, but I'm not getting anything as yet.
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd (0x27, 16,2);
float value=0;
volatile unsigned int rev;
//int wings= 1; // no of wings of rotating object, for disc object use 1 with white tape on one side
int rpm;
unsigned long oldtime=0;
unsigned long newtime;
void isr() //interrupt service routine
{
rev++;
}
void setup()
{
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);// power for IR sensor
lcd.init (); //initialize LCD
lcd. backlight (); // Turn on the backlight on LCD.
attachInterrupt(digitalPinToInterrupt(2),isr,FALLING); //attaching the interrupt
}
void loop()
{
delay(1000);
detachInterrupt(0); //detaches the interrupt
newtime=millis()-oldtime; //finds the time
rev=0;
int RPMnew = 1000*(rev/newtime); //here we used fan which has 3 wings
rpm= RPMnew*60; //calculates rpm
oldtime=millis(); //saves the current time
lcd.clear();
lcd.setCursor(0,0);
lcd.print("___TACHOMETER___");
lcd.setCursor(0,1);
lcd.print( rpm);
lcd.print(" RPM");
lcd.print(" ");
attachInterrupt(digitalPinToInterrupt(2),isr,FALLING);
}
No luck with this. I've put my scope on the O/P and am getting a good pulse O/P so dont think the issue is related to the IR sensor. The suggestion of time may be an issue, is there an example of your method I can look at ? (sorry I'm a noob)
Its a Colchester Student vari speed head I've just rebuilt and just trying to get this tacho working so I can actually use it! I've played with the guard on and off but with the same results.
rev=0;
int RPMnew = 1000*(rev/newtime); //here we used fan which has 3 wings
rpm= RPMnew*60; //calculates rpm
There are several things wrong with this code.
you set rev = 0 before using the value
if rev <1000 you get integer truncation with rev/newtime
You were given code @dlloyd in post #13. What do you see when you run that? When I test that code with a square wave it gives a reasonable value for rpm.
I have stripped away all the LCD and other stuff. Just the rpm code only, and added a bunch of print statements.
And I have to apologise for a total brain fog moment; the rev=0 should be after the variable has been used
float value=0;
volatile unsigned int rev;
//int wings= 1; // no of wings of rotating object, for disc object use 1 with white tape on one side
int rpm;
unsigned long oldtime = 0;
unsigned long newtime;
void isr() //interrupt service routine
{
rev++;
}
void setup() {
Serial.begin(115200);
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);// power for IR sensor
attachInterrupt(digitalPinToInterrupt(2),isr,FALLING);
//alternate
//attachInterrupt(digitalPinToInterrupt(2),isr,LOW);
}
void loop() {
newtime = millis()-oldtime; //finds the time
Serial.print("newtime: ");
Serial.println(newtime);
int RPMnew = 1000*(rev/newtime); // 1 piece of tape
Serial.print("RPMnew: ");
Serial.println(RPMnew);
rpm = RPMnew*60; //calculates rpm
Serial.print("rpm: ");
Serial.println(rpm);
oldtime = millis(); //saves the current time
rev=0;
}
And just please double check that the input pin is connected to pin2
Unfortunately, most examples just count the pulses each second, rather than measure the pulse period, which is much more accurate.
I want to eventually add a pulse measurement function to a library, so I modified your code to measure the pulse period in microseconds.
To test a known clean signal, disconnect your sensor from pin 2 then add a jumper from pin 2 to pin 3 (pwm@490Hz) . Your LCD will display 7350 RPM, which is 29,400 / 4 (wings).
This example will work well, providing the input signal is clean. Unfortunately, many of the sensor modules use a comparator circuit that has no hysteresis added, so I suspect switching noise on the signal coming from the sensor's comparator. reference
As time permits, I'll create a new simulation that adds noise to the rising and falling edges of an input pulse, then enhance the code to "debounce" the input, however, you may not need this.