Vehicle RPM Calculations Using the Arduino

Hello All,

Im looking to create a somewhat "universal" RPM guage with an RPM output for a vehicle (typically older vehicles that do not have OBDII). I will be pulling a pulse from a single cylinder as it fires. I want to run by all of you my plans and see if you agree with me or think there may be a better approch.

I plan to use inturrupts to count the time between each pulse, do the math and know the RPM. Not only do I want to capture the RPM but I also want to smooth it a bit as some ignition systems are a bit "ugly", so I plan to capture the last 5 pulse times average them, and then come up with my RPM. I then want to output a clean RPM signal at the spcified rate.

Know if my calcs suit me right... at 8000 RPM, I will have 4,000 pulses per minute (each cylinder fires once per 2 RPM), which works out to 66 pulses per second, .06 pulses per ms, which is roughly a pulse every 15ms...

Will this give the processor enough time to do the RPM calcs, output it to an LCD, and even reoutput a "clean" RPM pulse? Any suggestions on other ways you can think to do it?

Thanks,
Chris

The Arduino runs at 16Mhz and the timer ticks at 500 nanosecond so in theory you should be able to handoff any calculations to output in x microseconds....more than enough to real time rev display.

How do you intend to "pulling a pulse from a single cylinder as it fires"? I'm curious.

Not meaning to steal any thunder, but I have just the thing working with a software simulated 6 cylinder OBDII engine. It triggers a Timer input capture interrupt when the cam sensor fires (same event also causes timer reset). It also handles Timer rollover events for slow engine speeds. 16Mhz is plenty fast for RPM calcs (probably < 1% processor useage).

I'm not too familiar with pre-OBD vehicles. Are there typically no cam or crank sensors? I would think you'd want something a little less noisy (electrically) than picking up the ignition signal. Plus, I could be mistaken but isn't there a fairly common system where the spark fires every revolution, the second being without fuel charge, simply because it was easier to make? It might not be so universal if you pickup the spark signal.

Post some more of your thoughts, I'm interested in this project.

After-market tachs for older vehicles used to tap into the points circuit. A pulse was generated every time the points opened (or closed). The tachs had switches that could be set for 2, 4, 6, or 8 cylinders.

Typically, firing the spark plug every revolution is done only to two cylinder (motorcycle) engines. In that case, you simply told the tach that it was attached to a 4 cylinder engine, and it worked correctly.

I don't know how this stuff worked between points going away and all the computer stuff being standard. I lost interest in cars when the engine bay got filled up with so much add-on crap that you couldn't see the engine any more.

I plan to use inturrupts to count the time between each pulse, do the math and know the RPM. Not only do I want to capture the RPM but I also want to smooth it a bit as some ignition systems are a bit "ugly", so I plan to capture the last 5 pulse times average them, and then come up with my RPM. I then want to output a clean RPM signal at the spcified rate.

Know if my calcs suit me right... at 8000 RPM, I will have 4,000 pulses per minute (each cylinder fires once per 2 RPM), which works out to 66 pulses per second, .06 pulses per ms, which is roughly a pulse every 15ms...

Will this give the processor enough time to do the RPM calcs, output it to an LCD, and even reoutput a "clean" RPM pulse? Any suggestions on other ways you can think to do it?

As to timing, I have done something like this for a motorcycle which easily handled 8000 rpm. What I did in the interrupt routine was to process only every 10 tach pulses. Even at idle I'd be getting an update evry second and I never had to worry about overruns. My code was handling wheel interrupts as well as tach and just loading them into a queue or table. The "Loop" routine would pull them out of the table and do the calculations. That way I could do anything I liked for debugging and, worst case, I would find the table full and just flush data until I caught up. This did happen occasionally when I had very complex debugging or crappy signalling so it's worth checking.

As to data sources, I was pulling from a formal tach signal. You might find that most cars at least have that interface. I have also heard of catching a tach signal by wrapping a turn or two of wire around a spark plug lead but I don't know what signal level you'd get.

I would recommend getting it working for some particular car that you've got easy access to and go from there. Search for my stuff here and you may find the code or other people's comments of use.

Keep posting - I'm interested and others will be.

Be careful of your circuit interfaces to the car, the spikes can be wild. consider using an ipod/usb type power plug for your supply voltage to make life simpler.

I saw a basic tach for sale in an auto discount store for $20, again, might be a source of ideas.

Thank you all for your help on this! Here is what I have so far, and it seems to be working great when testing it with a simple push button to the inturrupt pin.

Next I will build the tach filter to take those harsh tach outputs I will be dealing with (with huge spikes). Below is my program thus far, and I will attach my filter I will be using also.

// Program to count pulses per revolution in an automobile
// TODO: Includes a running average to insure proper RPM output
// TODO: Output a clean averaged 5v Sq wave of RPM
// NOTE: May need to go to (us) as opposed to (ms) for better resolution

int Cycle = 0;                  // set to 0 for PulseStartTime and set to 
                        //   1 for PulseEndTime
unsigned long PulseStartTime;   // Saves Start of pulse in ms
unsigned long PulseEndTime;     // Saves End of pulse in ms
unsigned long PulseTime;        // Stores dif between start and stop of pulse
unsigned long RPM = 0;          // RPM to ouptut (30*1000/PulseTime)

void setup()
{
 Serial.begin(9600);            // OPENS SERIAL PORT SETS DATA TO 9600 bps
 attachInterrupt(0, RPMPulse, RISING); // Attaches interrupt to Digi Pin 2
}

void RPMPulse()
{
  if (Cycle == 0)                // Check to see if start pulse
  {
    PulseStartTime = millis();  // stores start time
    CycleOnOrOff = 1;           // sets counter for start of pulse
    return;                     // a return so it doesnt run the next if
  }
  if (Cycle == 1)             // Check to see if end pulse
  {
    detachInterrupt(0);         // Turns off inturrupt for calculations
    PulseEndTime = millis();    // stores end time
    Cycle = 0;                  // resets counter for pulse cycle
    calcRPM();                  // call to calculate pulse time
  }
}

void calcRPM()
{
  PulseTime = PulseEndTime - PulseStartTime; // Gets pulse duration
  Serial.print("PulseTime =");               // Output pulse time for debug
  Serial.print(PulseTime);                   // Pulse debug output
  Serial.print(" ");                         
  RPM = 30*1000/PulseTime*2;                 // Calculates RPM
  attachInterrupt(0, RPMPulse, RISING);      // re-attaches interrupt to Digi Pin 2
}

void loop()
{ 
 Serial.print("RPM = ");      // Output RPM for debug
 Serial.print(int(RPM));      // RPM debug output
 Serial.print(" ");
 delay(1000);                  // 1 sec delay for debug output
}

Here is the filter I plan to make:

Next step is to test it with the filter in a vehicle. Then add the averaging, then output a clean 5V sq wave RPM signal.

Thanks All!
I will keep you posted.

Chris

Alright here is what I have... The exact code above, but added some serial LCD commands so I could view things on a small LCD.

When I have a push button (with debouncing) hooked up to the interrupt pin (digi pin 2), all seems to work great. If I push the button wait about 1 sec press again, my LCD outputs roughly 60 RPM. If I wait two seconds and press the button, it output 30 RPM. So it seems my basic math is working great.

So it was on to the vehicle. I built my little tach filter (see design above), and got things wired into my vehicle. I used an o-scope to make sure my signals were good, and verified that the output on the filter looked good, which it was, as it was a pretty much a perfect sq wave 5V output, with a freq change as RPM increased or decreased.

Figured all was good to hook up the arduino with my program on it. Thats when things went kinda south... I started getting an RPM reading of -5536, or -2, and very rarely 30,000. The -5536 and the -2 was pretty random, but those were the only 3 values it would output, nothing else.

So I bring the whole setup back in, hook up my switch to the interrupt and everything outputs great. Please let me know if you have any suggestions or would like to see any information. I will re-attach my current code.

I will be tinkering some more tomorrow, do some debugging and get back to you all with my findings.

Thanks,
Chris

// Program to count pulses per revolution in an automobile
// TODO: Includes a running average to insure proper RPM output
// TODO: Output a clean averaged 5v Sq wave of RPM
// NOTE: May need to go to (us) as opposed to (ms) for better resolution

int Cycle = 0;                  // set to 0 for PulseStartTime and set to 
                        //   1 for PulseEndTime
unsigned long PulseStartTime;   // Saves Start of pulse in ms
unsigned long PulseEndTime;     // Saves End of pulse in ms
unsigned long PulseTime;        // Stores dif between start and stop of pulse
unsigned long RPM = 0;          // RPM to ouptut (30*1000/PulseTime)

void setup()
{
 Serial.begin(9600);            // OPENS SERIAL PORT SETS DATA TO 9600 bps
 Serial.print(0x0B,BYTE);      // LCD Setup
 Serial.print(0x40,BYTE);       // LCD Setup
 delay(100);
 Serial.print(0x1F,BYTE);       // LCD Setup
 Serial.print(0x28,BYTE);       // LCD Setup
 Serial.print(0x61,BYTE);      // LCD Setup
 Serial.print(0x40,BYTE);      // LCD Setup
 Serial.print(0x01,BYTE);      // LCD Setup
 Serial.print(0x0C,BYTE);      // LCD Setup
 delay(100);
 attachInterrupt(0, RPMPulse, RISING); // Attaches interrupt to Digi Pin 2
}

void RPMPulse()
{
  if (Cycle == 0)                // Check to see if start pulse
  {
    PulseStartTime = millis();  // stores start time
    CycleOnOrOff = 1;           // sets counter for start of pulse
    return;                     // a return so it doesnt run the next if
  }
  if (Cycle == 1)             // Check to see if end pulse
  {
    detachInterrupt(0);         // Turns off inturrupt for calculations
    PulseEndTime = millis();    // stores end time
    Cycle = 0;                  // resets counter for pulse cycle
    calcRPM();                  // call to calculate pulse time
  }
}

void calcRPM()
{
  PulseTime = PulseEndTime - PulseStartTime; // Gets pulse duration
  Serial.print("PulseTime =");               // Output pulse time for debug
  Serial.print(PulseTime);                   // Pulse debug output
  Serial.print(" ");                         
  RPM = 30*1000/PulseTime*2;                 // Calculates RPM
  attachInterrupt(0, RPMPulse, RISING);      // re-attaches interrupt to Digi Pin 2
}

void loop()
{ 
 Serial.print("RPM = ");      // Output RPM for debug
 Serial.print(int(RPM));      // RPM debug output
 Serial.print(" ");
 delay(1000);                  // 1 sec delay for debug output
 Serial.print(0x0C,BYTE);       // Clear LCD screen cmd
}
  1. What is CycleOnOrOff? Your posted code doesn't define it. (It looks like it should be just Cycle and you forgot to change it in one instance.)

  2. You can optimize by using type boolean for variable Cycle. Then void RPMPulse() becomes this...

void RPMPulse() {
  if (Cycle)                // Check to see if start pulse
  {
    PulseStartTime = millis();  // stores start time
    // CycleOnOrOff = 1;           // not defined.  Maybe this should be just "Cycle = true;" ??sets counter for start of pulse
  }
  else // otherwise it must be end pulse
  {
    detachInterrupt(0);         // Turns off inturrupt for calculations
    PulseEndTime = millis();    // stores end time
    Cycle = false;                  // resets counter for pulse cycle
    calcRPM();                  // call to calculate pulse time
  }
}
  1. Serial.print is very slow. At 9600 bps, to serial.print "PulseTime =" is 88 bits which is almost 10 ms just to clock out the data. I think you're probably getting some revolutions while the interrupt is detached and thus missing the pulse.
  • of all things I'm listing here I think this is the most likely cause for the garbage data you're seeing.
  1. An engine running at 3000 RPM = 50 Hz has a period of only 20 ms. If you're using millis(); to measure the period you have a quantization error of ~140 RPM (21 ms period = 2857 RPM). Is that precise enough? Calculate this at your redline too.

  2. You could optimize RPMPulse() to this (untested)...

void RPMPulse() {
  static unsigned long event_time = 0;
  unsigned long this_time = micros();

  RPM = 60000000 / (this_time - event_time);
  event_time = this_time;
}

Hello Mitch_CA,

Thank you for these suggestions, I will be trying them to see the results I get. You are correct the CycleOnOrOff should have just been Cycle. This has been fixed.

Awesome suggestion on setting the cycle just to a boolean as this simplifies things. I'll minmize the data I output to the LCD down to just the RPM figure, so that should help drop that to no more then 32 bits which should help.

I'll integrate in your optimization to use the micros instead of the millis. Thank you so much for these wonderful suggestions. Hopefully today I find some time to test them out!

Thanks again!
Chris

Hi all, can anyone tell me (a car novice) how to physically plug into an ignition pulse in a car or motorcycle? Fuse box or what? Can you take a picture? Also are you powering your arduino from the car battery?

I'm trying to figure out a way to turn on a device only when the engine is running and it seems the simplest way would be to check the "sparks". Having the rpm's would be a big bonus because I could use that to control the duty cycle for my device as well...

How about using a hall effect sensor? Wouldn't that detect the pulses if you simple taped it on a spark lead? :stuck_out_tongue:

cheers
Eiki

Do you really need the engine to be running or would having the key in "acc" or "on" position be enough.
There's all kinds of 12V power available that's hot only when the key is in "acc". Your stereo system works this way.

I really need the engine to be running (alternator) because the device draws alot of current and is also pointless when the engine isn't running since it's an hho booster...

Any ideas on sensing the ignition pulse like I asked? :-/

It might be time to make your own thread for your own problem here. But what you want to know is whether the alternator is turning, which can be presumed if the engine is turning. There are lots of ways to sense RPM.

Hi,

I beleive I'm simply asking how the other guys did theirs...but sure if no one answers here i'll ask somewhere else.

Hi all, can anyone tell me (a car novice) how to physically plug into an ignition pulse in a car or motorcycle? Fuse box or what? Can you take a picture? Also are you powering your arduino from the car battery?

I'm trying to figure out a way to turn on a device only when the engine is running and it seems the simplest way would be to check the "sparks". Having the rpm's would be a big bonus because I could use that to control the duty cycle for my device as well...

How about using a hall effect sensor? Wouldn't that detect the pulses if you simple taped it on a spark lead? :stuck_out_tongue:

most cars, even old and simple ones, will have a tachometer connection somewhere that you can hook up to. It's likely to be in the wiring harness behind the dash rather than at a fuse box. It will be 12 volts and may be messy(see the filter circuit pointed to above). Try ro find a wiring diagram for your car. I have heard of capturing the ignition pulse with a few turns of wire wrapped around a plug lead but I have no idea exactly how that would work.

Also, maybe another idea: If you look at your battery voltage, you'll find that the voltage is much higher - like 14V or more when it's running and as low as 11 when it's not running. That would be pretty easy to check for and it seems particularly useful for you because you care about having the juice available for your device.

Thanks for the reply. I'm gonna try to get some info from mechanics I know about finding the "tach" signal to leech. I have the option of doing an OBDII connection for reading but I need to have a fairly universal solution.

About the battery and if the engine in running. It is true that the voltage goes up to 14V when the engine is running (13.8V) on the majority of cars. I could perhaps do a simple voltage divider to scale that down to be readable to the arduino?

What about the current though...I'm afraid of frying the Arduino on the first test.

The Arduino will source the current it needs (a tiny amount) on the analog and digital pins. The voltage needs to be dropped to +5V, but the current shouldn't be a problem.

Drawing too much current FROM the Arduino is a problem, though.

From the arduino? :slight_smile:
Could you elaborate on that.

This clearly exposes my hobbyist status in electronics (I'm a programmer with some EE studies behind me). I have a fundamental boggle in my thinking of current. Text books talk about current DRAW but then always use the analogy of a water pipe system which obviously doesn't draw water (current) but pushes it, right?

This is very much off topic and newbie-ish but you are saying that if I hook up an arduino/acmedevice on a wire that is already drawing/pulling 10A to the next device on the wire the Arduino is only going to take what it needs?...unless there is enough potential (voltage) on the wire as well?