Go Down

Topic: Vehicle RPM Calculations Using the Arduino (Read 23705 times) previous topic - next topic

calcdffirefighter

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

nikinick

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.

PaulS

How do you intend to "pulling a pulse from a single cylinder as it fires"? I'm curious.
The art of getting good answers lies in asking good questions.

mitch_79

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.

PaulS

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.
The art of getting good answers lies in asking good questions.

bill2009

Quote
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.

calcdffirefighter

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.

Code: [Select]

// 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:
http://cid-47a876756f298005.skydrive.live.com/self.aspx/.Public/TachFilter.JPG

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

calcdffirefighter

#7
Nov 24, 2009, 12:53 am Last Edit: Nov 24, 2009, 12:55 am by calcdffirefighter Reason: 1
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

Code: [Select]

// 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
}

mitch_79

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...
Code: [Select]

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
 }
}


3) 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.

4) 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.

5) You could optimize RPMPulse() to this (untested)...
Code: [Select]
void RPMPulse() {
 static unsigned long event_time = 0;
 unsigned long this_time = micros();

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


calcdffirefighter

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

sputnick

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? :p

cheers
Eiki

mitch_79

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.

sputnick

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...

sputnick

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

mitch_79

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.

Go Up