help in making engine digital RPM meter

first I want to ask that should I convert the frequency to 0 - 5 volt ( taking Vout = 5 volt at 7000 Rpm) and read it with the analog input pin..??

so my 0 - 7000 RPM will be analog read rpm = map(rpm, 0, 1023, 0, 7000)

OR

There is other better way to do it for better precession and accuracy..??

There is other better way to do it for better precession and accuracy..??

None of what you suggest here will make for better precision or accuracy. Multiplying an inaccurate value won't make it more accurate.

Getting the signal each and every time, at the exact time it is sent, is how you improve accuracy. Precision is rather meaningless in this context. Either you got the pulse or you didn't. There isn't any middle ground.

If I may add to Pauls, also depend on the rotation device ( ei : your bike motor )

So should I just go for -

convert the frequency to 0 - 5 volt ( taking Vout = 5 volt at 7000 Rpm) and read it with the analog input pin..??

so my 0 - 7000 RPM will be analog read rpm = map(rpm, 0, 1023, 0, 7000)

convert the frequency to 0 - 5 volt

Frequency of what? How will you convert that frequency to a voltage?

PaulS:

convert the frequency to 0 - 5 volt

Frequency of what? How will you convert that frequency to a voltage?

PaulS this is the IC which converts frequency to voltage

http://www.national.com/ds/LM/LM2907.pdf

and the calculation for it is

Vout = Vcc * Fin * R1 * C1

Converting what is essentially a digital signal to analog via a frequency-to-voltage chip and then converting it back to digital strikes me as being a very silly way to make a digital RPM meter. Much better is to:

  1. Clean up the input pulse if necessary so that you get one (or more) well-defined pulse(s) per revolution (you would have to do this anyway if you use a frequency-to-voltage chip);

  2. Feed the pulse into an Arduino pin that supports interrupts. In the ISR, record the time that the pulse occurred using a call to micros(), and calculate the time since the previous pulse. Then the RPM is just 60 divided by the interval (in seconds) between pulses.

dc42:
Converting what is essentially a digital signal to analog via a frequency-to-voltage chip and then converting it back to digital strikes me as being a very silly way to make a digital RPM meter. Much better is to:

  1. Clean up the input pulse if necessary so that you get one (or more) well-defined pulse(s) per revolution (you would have to do this anyway of you use a frequency-to-voltage chip);

  2. Feed the pulse into an Arduino pin that supports interrupts. In the ISR, record the time that the pulse occurred using a call to micros(), and calculate the time since the previous pulse. Then the RPM is just 60 divided by the interval (in seconds) between pulses.

You are right.. I even had the same thought in my mind that why convert frequency to voltage and feed arduino when arduino itself can take the frequency directly..

If you read my posts right from the beginning, you will notice thats what I wanted to do ..

I wanted to find the time gap between pulses and calculate the RPM...But I just couldn't get how to find the interval...

can you please help me with the coding as you said..??

OK, I'll assume you are using an Arduino Uno. This can generate interrupt 0 on pin 2 and interrupt 1 on pin 3. I'll assume you are feeding the ignition pulse (cleaned up if necessary) to pin 2. Here is a sketch to calculate the rpm:

const int ignitionPin = 2;
const int ignitionInterrupt = 0;
const unsigned int pulsesPerRev = 1;

unsigned long lastPulseTime = 0;
unsigned long rpm = 0;

void ignitionIsr()
{
  unsigned long now = micros();
  unsigned long interval = now - lastPulseTime;
  if (interval > 2000)
  {
     rpm = 60000000UL/(interval * pulsesPerRev);
     lastPulseTime = now;
  }  
}

void setup()
{
  pinMode(ignitionPin, INPUT);
  attachInterrupt(ignitionInterrupt, &ignitionIsr, RISING);
}

void loop()
{
  // insert code here to show the RPM on the display and delay for e.g. 0.2 seconds
}

I've included some pulse cleanup in the software (i.e. ignore pulses that are less than 2ms apart).

Thank you so much... :slight_smile:

I have a Arduino duemilanove with the Atmega328.. Hope it will also able to take interrupts as the uno on the same pins.. as the uno also uses the 328...

have a Arduino duemilanove with the Atmega328.. Hope it will also able to take interrupts as the uno on the same pins.. as the uno also uses the 328...

As far as software code and hardware pin numbers there is no difference between Uno and your board. Only difference for Uno is smaller size bootloader that also uploads at a faster baudrate, everthing else is the same.

Lefty

dc42:
OK, I'll assume you are using an Arduino Uno. This can generate interrupt 0 on pin 2 and interrupt 1 on pin 3. I'll assume you are feeding the ignition pulse (cleaned up if necessary) to pin 2. Here is a sketch to calculate the rpm:

const int ignitionPin = 2;

const int ignitionInterrupt = 0;
const unsigned int pulsesPerRev = 1;

unsigned long lastPulseTime = 0;
unsigned long rpm = 0;

void ignitionIsr()
{
 unsigned long now = micros();
 unsigned long interval = now - lastPulseTime;
 if (interval > 2000)
 {
    rpm = 60000000UL/(interval * pulsesPerRev);
    lastPulseTime = now;
 }  
}

void setup()
{
 pinMode(ignitionPin, INPUT);
 attachInterrupt(ignitionInterrupt, &ignitionIsr, RISING);
}

void loop()
{
 // insert code here to show the RPM on the display and delay for e.g. 0.2 seconds
}




I've included some pulse cleanup in the software (i.e. ignore pulses that are less than 2ms apart).

@ dc42

First tell me how do I setup my hardware portion of pulse input to the arduino board..??
I thought of using something like this http://www.sportdevices.com/rpm_readings/index.htm
or
tell me if there is any other way to pickup and feed the pulse which will just match your coding..

I went through your code, its just what I want to do, but couldnt plot out how to do it as I am not much advance in coding..

will you please make me understand this part... rpm = 60000000UL/(interval * pulsesPerRev); what is that 60000000UL...??

@Joy

Connect the output ( pin 3 ) of the 555 to digital pin 2. It is the interrupt # 0.

Here why :

const int ignitionInterrupt = 0;

attachInterrupt(ignitionInterrupt, ignitionIsr, RISING);

Here the reference : http://arduino.cc/en/Reference/AttachInterrupt

OK..

I just rewrote the code..Please correct me in places where I am wrong..

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 9, 8);

const int ignitionPin = 2;
const int ignitionInterrupt = 0;
const unsigned int pulsesPerRev = 1;

unsigned long lastPulseTime = 0;
unsigned long rpm = 0;

void ignitionIsr()
{
  unsigned long now = micros();
  unsigned long interval = now - lastPulseTime;
  if (interval > 2000)
  {
     rpm = 60000000UL/(interval * pulsesPerRev);
     lastPulseTime = now;
  }  
}

void setup()
{
  lcd.begin(16,2);
  pinMode(ignitionPin, INPUT);
  attachInterrupt(ignitionInterrupt, &ignitionIsr, RISING);
}

void loop(){
  lcd.setCursor(0, 0);
  lcd.print("RPM- ");
  lcd.print(rpm);
  delay(200);
 
}

Your code looks good to me, although as you are feeding it with negative-going pulses, I suggest changing RISING to FALLING in the attachInterrupt call.. The 555 circuit is probably overkill, but should work, and you can connect the 555 output to pin 2 of the Arduino as Techone says. If you want to try something simpler, just take the left hand half of the circuit (transistor, 2 resistors, diode, capacitor) and feed the output from the transistor into the Arduino pin 2 instead of into the 555.

The line:

rpm = 60000000UL/(interval * pulsesPerRev);

says take 60 million (which is the number of microseconds in 1 minute) and divide it by the interval in microseconds betweern pulses mutiplied by the number of pulses per revolution (which is 1 for your 2-stroke single cylinder bike). The UL suffix on the 60 million says that you want it interpreted as an unsigned long integer, because 60 million is too big to fit in an int.

WOW...

I tested it with a pulse generated by the 555 and counted the no. of blinks of the led attached on output of 555 and matched it with the reading on the led of arduino...
It was fully perfect... :slight_smile:

Now. the only thing is that i am trying to do is when the rpm reaches 1000 and above I want to put a comma ( , ) after the thousand unit ( for eg.. if the rpm is 1500 I want a comma after 1. So it should look like 1,500)..

I wrote a thread on this but helpful members are unable to understand me..

void loop()
{
  noInterrupts();
  unsigned long rpmToDisplay = rpm;  // take a copy with interrupts disabled in case it changes
  interrupts();
  if (rpmToDisplay >= 1000)
  {
    Serial.print(rpmToDisplay/1000);
    Serial.print(',');
    Serial.print((char)(((rpmToDisplay/100) % 10) + '0'));
    Serial.print((char)(((rpmToDisplay/10) % 10) + '0'));
    Serial.print((char)((rpmToDisplay %10) + '0'));
  }
  else
  {
    Serial.print(rpmToDisplay);
  }
  Serial.println(); 
  delay(200);
}

I've coded and tested it with Serial.print but it should work with lcd.print as well.

dc42:

void loop()

{
 noInterrupts();
 unsigned long rpmToDisplay = rpm;  // take a copy with interrupts disabled in case it changes
 interrupts();
 if (rpmToDisplay >= 1000)
 {
   Serial.print(rpmToDisplay/1000);
   Serial.print(',');
   Serial.print((char)(((rpmToDisplay/100) % 10) + '0'));
   Serial.print((char)(((rpmToDisplay/10) % 10) + '0'));
   Serial.print((char)((rpmToDisplay %10) + '0'));
 }
 else
 {
   Serial.print(rpmToDisplay);
 }
 Serial.println();
 delay(200);
}




I've coded and tested it with Serial.print but it should work with lcd.print as well.

Your code just worked like a charm... Felt so relief that u atleast understood and solved what I wanted to print... :slight_smile:

I am having a little problem with the delays in my rewritten code..
Please check out if there is any other way..

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 9, 8);
const int analoginput = 0;
float vout = 0.0;
int value = 0;
float R1 = 1000.0;    // !! resistance of R1 !!
float R2 = 470.0;     // !! resistance of R2 !!
float vin = 0.0;
const int ignitionPin = 2;
const int ignitionInterrupt = 0;
const unsigned int pulsesPerRev = 1;

unsigned long lastPulseTime = 0;
unsigned long rpm = 0;

void ignitionIsr()
{
  unsigned long now = micros();
  unsigned long interval = now - lastPulseTime;
  if (interval > 2000)
  {
     rpm = 60000000UL/(interval * pulsesPerRev);
     lastPulseTime = now;
  }  
}


void setup()
{
  lcd.begin(16,2);
 
  pinMode(analoginput, INPUT);
  pinMode(ignitionPin, INPUT);
  attachInterrupt(ignitionInterrupt, &ignitionIsr, FALLING);
}

void loop()
{
  /***************THIS PART IS FOR VOLTAGE*********************/
  lcd.clear();
   // read the value on analog input
 value = analogRead(analoginput);
 vout= (value * 5.0)/1024.0;  //voltage coming out of the voltage divider
 vin = vout / (R2/(R1+R2));  //voltage to display
 
   
 lcd.setCursor(0,0);
 lcd.print(vin, 1);   //Print float "vin" with 1 decimal
 lcd.print("V");
 
 
 /****************THIS PART IS FOR RPM*************************/
  noInterrupts();
  unsigned long rpmToDisplay = rpm;  // take a copy with interrupts disabled in case it changes
  interrupts();
  if (rpmToDisplay >= 1000)
  {
    lcd.setCursor(6, 0);
    lcd.print("RPM");
    lcd.print(rpmToDisplay/1000);
    lcd.print(',');
    lcd.print((char)(((rpmToDisplay/100) % 10) + '0'));
    lcd.print((char)(((rpmToDisplay/10) % 10) + '0'));
    lcd.print((char)((rpmToDisplay %10) + '0'));
    
  }
  else
  {
    lcd.setCursor(6, 0);
    lcd.print("RPM ");
    lcd.print(rpmToDisplay);
    
  }
   
  delay(200);
  
}

In this code I want to call for RPM print every 0.2 seconds... But I want to call for Voltage every 1.4minutes..

But that delay(200); is call my voltage at the same intervals...
How to solve this...??

First, your loop() function is getting rather complicated, so best split out the bits that display voltage and rpm into separate functions.

Next, you want to display voltage every 1.4 minutes i.e. 84 seconds (I wonder where you got this figure from?) and rpm every 0.2 seconds. So if you only update the voltage every 84/0.2 = 420 iterations of the loop, that should do it. Something like this (warning - untested code!):

void displayRpm()
{
  noInterrupts();
  unsigned long rpmToDisplay = rpm;  // take a copy with interrupts disabled in case it changes
  interrupts();

  lcd.setCursor(6, 0);
  lcd.print("        ");    // clear old RPM display in case the new value is shorter
  lcd.setCursor(6, 0);
  if (rpmToDisplay >= 1000)
  {
    lcd.print("RPM");
    lcd.print(rpmToDisplay/1000);
    lcd.print(',');
    lcd.print((char)(((rpmToDisplay/100) % 10) + '0'));
    lcd.print((char)(((rpmToDisplay/10) % 10) + '0'));
    lcd.print((char)((rpmToDisplay %10) + '0'));    
  }
  else
  {
    lcd.print("RPM ");
    lcd.print(rpmToDisplay);    
  }
}

void displayVoltage()
{
  value = analogRead(analoginput);
  vout = (value * 5.0)/1024.0;  //voltage coming out of the voltage divider
  vin = vout * ((R1 + R2)/R2);  //voltage to display
  
  lcd.setCursor(0,0);
  lcd.print("     ");   // clear old value in case the new value is shorter
  lcd.setCursor(0,0);
  lcd.print(vin, 1);   //Print float "vin" with 1 decimal
  lcd.print("V");
}

const int maxCount = 420;

int loopCount = maxCount;

void loop()
{
  displayRpm();
  if (loopCount == maxCount )
  {
    loopCount = 0;
    displayVoltage();
  }
  ++loopCount;
  delay(200);
}

You may also need to do an lcd.clear() at the end of setup().