Use arduino to measure and record engine RPM

I'm trying to use an Arduino Uno to measure engine RPM using the coil. I'm not exactly sure how it's done, but I know it uses the electrical pulses to determine the RPM.

I came across this thread: http://arduino.cc/forum/index.php?topic=10219.0

It helped a little bit but not completely. I don't have any way of getting a digital signal, so I'll be stuck with an analog signal. I will have to wire in a couple of resistors to bring the voltage down to 5V.

It says that you should use the negative side of the coil but I thought it would have to be the positive. Does anyone know why?

More importantly, I don't know how I would code it to interpret the pulses and translate it into RPMs, then record it for later graphing.

Any help is appreciated. Thank you.

lnz423: It says that you should use the negative side of the coil but I thought it would have to be the positive. Does anyone know why?

If you are talking about the primary side of the ignition coil, then the traditional wiring is that the positive side of the coil goes to +14V via the ignition key switch, and the negative side is connected to ground via the contact breaker (in old cars) or transistor. So the positive terminal of the coil is at a constant +14V. It is the negative terminal that carries a pulsed signal, ranging from 0V when the contacts are closed or the transistor is turned on, then producing a high voltage positive pulse, eventually settling at +14V until the contacts close again.

The usual way of measuring RPM is to condition the input signal using a transistor and a few resistors, and feed the resulting into one of the Arduino's interrupt pins. In the interrupt service routine, you read the time using micros(), calculate the time since the last interrupt, and store the time ready for the next interrupt. From the time between interrupts, you calculate the RPM.

You have only 1K bytes of EEPROM in the atmega328p used on the Uno, so if you want to store much RPM data, then you will need to write it to an SD card or similar.

Excellent! Thank you so much for the reply.

It's apparent that I have a lot more research to do. I'm just now getting into electronics and I'm taking a couple of electrical engineering classes at school (I'm a mechanical engineering major).

So I would need some resistors and a transistor along with the Arduino?

Also, could I have the Arduino send the data back to my computer directly instead of storing to an SD card? (I was planning to buy an Arduino Uno)

(Oh, and it is an old car, 1966 Mustang but it has a Pertronix electronic distributor and coil, which I'm assuming has the transistor system)

Ok well I've tried to work on this and I've used this example: http://playground.arduino.cc/Main/ReadingRPM to get all my code.

I pretty much followed it exactly except for changing some of the variables and obviously different rpm calculations to suit my application.

The problem I'm facing now is that it is showing extremely large values for rpm, even when it's not connected to anything.

I have the if loop just like the example and the Serial.println is inside the if loop and it should only go into that if loop if the rpmcount is larger than the value that I set, but it still prints numbers.

I have an Arduino Uno as stated before, and I was using this as my interrupt function "attachInterrupt(0, rpmtrigger, LOW);"

but I think that might not be the correct way to put it going by this: http://arduino.cc/en/Reference/AttachInterrupt

It says it should be like this "attachInterrupt(interrupt, function, mode);" but I don't know what the "interrupt" means What do I put there?

Any help would be greatly appreciated. I can post all my code if needed.

but I don't know what the "interrupt" means

0 or 1 on the Uno, which correspond to pins 2 and 3 respectively.

Post your code & circuit. From the symptoms though, it sound like the input is floating. A pullup or pulldown resistor would help if that's the case - don't forget that the Arduino has interal pullups you can use.

lnz423: I have an Arduino Uno as stated before, and I was using this as my interrupt function "attachInterrupt(0, rpmtrigger, LOW);"

You'll need to use interrupt mode RISING or FALLING (it doesn't matter which), not LOW.

wildbill:

but I don't know what the "interrupt" means

0 or 1 on the Uno, which correspond to pins 2 and 3 respectively.

Post your code & circuit. From the symptoms though, it sound like the input is floating. A pullup or pulldown resistor would help if that's the case - don't forget that the Arduino has interal pullups you can use.

Thanks. I'll try to post my code after I fix it up so I don't look so dumb lol. I'm not sure if I made sense in my prior post, but I'll clarify just in case:

"attachInterrupt(interrupt, function, mode) attachInterrupt(pin, function, mode) (Arduino Due only) "

The above text is posted in attachInterrupt() reference page on the Arduino website. This made me believe that on all boards besides the Due, you would need to add some variable like:

int interrupt = 0 ... attachInterrupt(interrupt, etc...)

instead of just putting the pin number as the first parameter.

dc42:

lnz423: I have an Arduino Uno as stated before, and I was using this as my interrupt function "attachInterrupt(0, rpmtrigger, LOW);"

You'll need to use interrupt mode RISING or FALLING (it doesn't matter which), not LOW.

Thanks! I changed it to FALLING and it seems to be working better. I have made a test circuit with a button and it's working quite well. I'll test it on the engine tomorrow and see if it works. Edit: It's only giving me multiples of 10: 100, 120, 200 etc. Anyone have an idea why?

Also, I would like to print the rpm and the time at which that rpm was recorded so that I can plot them out correctly. How could I print both at once? (I have searched this on all the forums and can't find a way for me to do it.)

It's only giving me multiples of 10: 100, 120, 200 etc. Anyone have an idea why?

You'll need to look at the codes' maths for that.... There will be a line where it converts the pulses' timings in milli- or micro-seconds into rpm. Might be something to do with the number of pulses per revolution (4 stroke engine, how many cylinders?- there were 6's and 8's of that model I guess?)

I would like to print the rpm and the time at which that rpm was recorded so that I can plot them out correctly. How could I print both at once?

Presumably you have a variable like RPM and using millis() or micros() to keep the time? You'll probably be using a Serial.println(RPM); so just use something like this which will print them side by side:

Serial.print(RPM);
Serial.print("\t");              // prints a tab
Serial.println(millis());       // or micros()

JimboZA:

It's only giving me multiples of 10: 100, 120, 200 etc. Anyone have an idea why?

You'll need to look at the codes' maths for that.... There will be a line where it converts the pulses' timings in milli- or micro-seconds into rpm. Might be something to do with the number of pulses per revolution (4 stroke engine, how many cylinders?- there were 6's and 8's of that model I guess?)

Ok yeah I've done some more research and I was able to print the time and RPM on the same line. And I think I may have stumbled upon the problem causing my RPM to only print multiples of 10. While I was trying to print the time in seconds, I tried dividing millis() by 1000 and was only getting times like 5.00 without the precise decimals. I found out that I have to use the modulo operator and print that after the seconds to complete the decimal.

I'll just post my code and make it easier for everyone lol. Here goes, but remember I'm new to programming.

volatile byte rpmcount = 0;
unsigned long rpm = 0;
unsigned long timeold = 0;
int interrupt = 0;

void setup()
{
  Serial.begin(9600);
  attachInterrupt(interrupt,rpmtrigger,FALLING);

}
void loop()
{
  if (rpmcount >= 20)
  {
    rpm=((15000)/(millis()-timeold))*rpmcount;
    timeold = millis();
    int time = (millis()/1000);
    int timedec = (millis()% 1000);
    rpmcount = 0;
    Serial.print("RPM = ");
    Serial.print(rpm);
    Serial.print(" Time = ");
    Serial.print(time);
    Serial.print(".");
    Serial.print(timedec);
    Serial.println();
  }
  
}
void rpmtrigger()
{
  rpmcount++;
}

I'll just post my code and make it easier for everyone

But are you still asking a question?- looks like you've got it sorted...

JimboZA:

I'll just post my code and make it easier for everyone

But are you still asking a question?- looks like you've got it sorted...

Yeah I asked the question before I got it sorted. I hadn't figured it out yet. Should I repeat what I did for the time for the rpm? Would I have to first calculate the value without the decimal, then use modulo to get the rest of it?

Edit: Yeah I finally understand. I feel dumb now. Thanks everyone! I appreciate all the help and I hope I haven't been too annoying!

Just as a matter of interest, have you got the number of coil pulses per revolution vs number of cylinders correct? For a four-stroke, the number of pulses per revolution is 1/2 the number of cylinders.... a 6 gives 3ppr, and 8 gives 4ppr. (See here for instance, where a tach manufacturer has switches on their products to set the correct values.

Yes I have a V8 which will give me 4 pulses per 1 crankshaft revolution. After doing the math, I arrived at the calculation shown in my code.

I’m almost done with the code, but I haven’t yet figured out how to add the decimal part back to the nondecimal part for the rpm.

A 289?

It was originally a 289 but when I bought it, the previous owner had put in a very worn 302 with mismatched parts. So I bought a long block 302 and built up the rest and swapped it a couple of years ago. It's carbureted with electronic ignition from Pertronix.

Well the part I'm stuck on now is that if, for example, the number of seconds is some arbitrary amount like 2248 ms, then the rpm value should be 133.45 when put into the formula I'm using, but the code will only give me 120. I'll be missing 13.45 which I'll have to get somehow using the same strategy I used to the time. But if I use 15000 % (millis()-timeold), it will give me something like 672 or 67. So if I use that to multiply with rpmcount, instead of the 13.45 that I'm missing, I'll get 1345 which I can't use it to add to the 120. So what can I do? If I divide by 100 to get 13 and do it again to get the 45, I can add the 13 and display both, but it won't work for all cases. I would have to use an if statement. And I feel like that's the wrong way to do it. Any suggestions?

Edit: I don't care about the decimals. I don't want it to be that precise so forget about that. but I still need to add the 13, so I would need to take the 13 out of some value like 1345.

I didn't check, but it's probably rounding. Make the 15000 15000UL and multiply by rpm before you do the division.

Ok I hooked it up to the coil and was getting about the right numbers, but off by a factor of ten. I would get numbers like 6890. They were all in the thousands, so I changed the 15,000 to 1,500 and it was giving me the right number of digits and now my data were all perfect.

Here is some sample output after the change:

RPM = 652 Time = 1.966 RPM = 728 Time = 2.172 RPM = 718 Time = 2.385 RPM = 714 Time = 2.595 RPM = 691 Time = 2.812 RPM = 721 Time = 3.22 RPM = 701 Time = 3.240 RPM = 710 Time = 3.451 RPM = 707 Time = 3.664

And here is the final code used to get that output:

volatile byte rpmcount = 0;
unsigned long rpmnum = 0;
int rpmdec = 0;
unsigned long rpm = 0;
unsigned long timeold = 0;
int interrupt = 0;

void setup()
{
  Serial.begin(9600);
  attachInterrupt(interrupt,rpmtrigger,FALLING);

}
void loop()
{
  if (rpmcount >= 100)
  {
    rpmnum =(1500L*rpmcount);
    int rpmtime= (millis()-timeold);
    rpm = ((rpmnum)/rpmtime);
    timeold = millis();
    int time = (millis()/1000);
    int timedec = (millis()% 1000);
    
    Serial.print("RPM = ");
    Serial.print(rpm);
    Serial.print(" Time = ");
    Serial.print(time);
    Serial.print(".");
    Serial.print(timedec);
    Serial.println();
    rpmcount = 0;
  }
  
}
void rpmtrigger()
{
  rpmcount++;
}

Now the problem is, I don't understand why it's off by a factor of 10. I did the calculations over and over and I can't figure out why. I even worked backwards to try to verify the numbers. If you take the last two points from the sample I posted above: RPM = 710 Time = 3.451 RPM = 707 Time = 3.664

the time between them is about 213 milliseconds so I found out that at this rpm, in 213 milliseconds, it should give me 10 pulses instead of the 100 that I had it set to collect data after. So why is it off by a factor of ten? Can anyone figure it out? Does it mean that my data is wrong and it's a coincidence that they are reasonable numbers?

Is there a way that on one pulse of my engines voltage falling, the interrupt is being triggered several times? Is there a way I can put some sort of "if" statement in an interrupt to make sure the interrupt is triggered only once per pulse?

Does anyone know how to make sure the interrupt is triggered once per pulse?

15000 is correct:

revsPerSecond = revs/sampleTime

So:

rpm = 60 * revs/sampleTime

If we measure sampleTime in milliseconds instead of seconds:

rpm = 60000 * revs/sampleTime

But your engine gives 4 pulses per revolution, so:

rpm = 60000 * (pulses/4)/sampleTime = 15000 * pulses/sampleTime

btw:

  1. millis() isn't very accurate, you'd be better off using miros().

  2. To get a finer resolution, have the ISR measure the time in microseconds between the current pulse and the previous one. Store that for the main program to read and calculate the RPM from. You will need a mechanism to determine when the engine has stopped rotating, i.e. no interrupts received for a while, so that you can display zero.

  3. If you're worried about getting multiple interrupts per spark, in the ISR you can ignore any interrupt if the previous interrupt was too recent.