A tachometer (REV counter) using a ugly signal coming from the ignition coil

Hi, This is my first post here. I've taken a Arduino beginners course a while ago and have made some useful (and some not so useful) projects since I've finished the course.

My car (from the 1960's) does not have a tachometer (REV counter). So one of my latest projects is to make a digital tachometer. The idea is to use the signal from the negative side of the ignition coil. To lower the voltage I've made a voltage divider. The signal then connects to the interrupt pin on the Arduino and the ground of the Arduino is connected to the negative battery terminal of the car. To display the RPM a 4x7segment display is connected to the Arduino.

I'm using the following code to measure and display the RPM:

#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 3
#define DIO 4
TM1637Display display(CLK, DIO);

const uint8_t SEG_ERR[] = {
  0x0,
  SEG_A | SEG_D | SEG_E | SEG_F | SEG_G,
  SEG_E | SEG_G,
  SEG_E | SEG_G
  };

const uint8_t SEG_HOI[] = {
  0x0
  SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,
  SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,
  SEG_E | SEG_F 
  };

float rev=0;
int c=8;          //number of cylinders
int rpm;
int oldtime=0;
int time;

void isr(){     //interrupt service routine 
  rev++;
}

void setup() {
  display.setBrightness(4); //0-4
  display.clear();
  display.setSegments(SEG_HOI);   //display welcome message
  delay(2000);
  attachInterrupt(0,isr,RISING);  //attaching the interrupt
}

void loop() {
  delay(100);
  detachInterrupt(0);           //detaches the interrupt
  time=millis()-oldtime;        //finds the time 
  rpm=((rev/time)*60000)/c;     //calculates rpm
  oldtime=millis();             //saves the current time
  rev=0;

  if (rpm<0){
    rpm=0;
  }
  if (rpm>7000){
    rpm=7000;
  }

  if (rpm=<7000){
    display.showNumberDec(rpm, false);
  }
  else{
    display.setSegments(SEG_ERR); //Show error message if rpm > 7000
  }

  attachInterrupt(0,isr,RISING);
 
}

It however does not give the RPM I was expecting. Is was more like double of what I was expecting. So I've connected my oscilloscope. And here is the result;
Oscilloscope connected directly to the ignition coil: directly connected.jpg
directly connected.jpg
The video shows the signal with the voltage divider (the signal the Arduino receives)
Video: MOV 1025 - YouTube

I'm guessing the reason it's not showing the correct RPM has to do with the ugly signal. It looks like the Arduino most of the time may detect not 1 but 2 pulses per "ignition spark".

Does anybody have any suggestions on how to make this work? Simply dividing the RPM by 2 in the code is not the right way because it sometimes also detects just 1 pulse per spark.

directly connected.jpg

You do recall the crank must rotate TWICE for every time the spark is delivered.

Paul

this is an ongoing problem with VDO tachs. you would think they would fix it internally and be done with it. how they fix it:

VDO tach fix

this does not work on my baja bug. connecting a tach / dwell meter in parallel does work

I made an RPM, a digital speed indicator, and some more to my car a long time ago. It ran for 10 years without any problem. Connect to the low voltage side of the ignition coil and use an optocoupler and a threshold circuitry to catch/deliver the pulse signal You will feed to Your controller. Then measuring the time between pulses and adjusting to the number of cylinders etc. is no big deal. Some math.

You need to have a "dead time" after each interrupt, so that further interrupts within that dead time do not increment the counter. It's a lot like debouncing a button. You need to calculate how long that dead time should be by knowing the maximum revs the engine should ever reach, calculating the interval between interrupts, in microseconds, at that maximum revs, and choosing a time a little less than that. Then, in the ISR, you can check if enough time has passed since the counter was last incremented. If it has, increment the counter and calculate the earliest time for the next increment. If not, don't increment the counter.

Did you try:

attachInterrupt(0,isr,FALLING);

? Did you try using nointerrupts() , interrupts() , instead of detaching and reattaching? https://www.arduino.cc/reference/en/language/functions/interrupts/nointerrupts/

Conditioning the signal Before feeding it to the controller looks neccessary to me. To "filter out" a valid signal surronded by ghost signals, like a rain of signals, interrupts, can't give a proper reading.

Counting ignition pulses versus the rate of update gives a conflict. How many pulses are needed to give a reasonable resolution? +- 10 RPM at some thousand RPM? Say 3600 RPM and 4 cylinders. That is 60 revs per second, 2 sparks per rev, 120 pulses that will be multiplied by 30 to show 3600 RPM.

  if (rpm>7000){
    rpm=7000;
  }

  if (rpm=<7000){
    display.showNumberDec(rpm, false);
  }
  else{
    display.setSegments(SEG_ERR); //Show error message if rpm > 7000
  }

This is never going to show “Err”, because rpm will never be over 7000.

Might be as simple as adding

delayMicroseconds(100) within the isr to keep it from retriggering on the after-pulse.

You will know how many times you paused in the isr so you could easily add that back into your “time” for processing.

Some filtering circuits you might be able to adapt here.......

https://www.sportdevices.com/ignition/ignition.htm

First, thank you all for responding to my question! Please excuse me for my bad grammar and/or wrong/strange word choice. English is not my native language.

A solution which does not require additional components/circuitry is preferred.

Paul_KD7HB:
You do recall the crank must rotate TWICE for every time the spark is delivered.

Paul

Please explain. If I recall correct the rotor (in the distributor) makes 1 full rotation per engine revolution wich in turn gives the number of cylinders spark-pulses (8 in my case) per engine revolution. Or am I wrong?

[quote author=Geek Emeritus date=1559240844 link=msg=4193479]
this is an ongoing problem with VDO tachs. you would think they would fix it internally and be done with it. how they fix it:

VDO tach fix

this does not work on my baja bug. connecting a tach / dwell meter in parallel does work[/quote] I’ll keep this in mind in case I can’t come up with a working “software-solution”.

Railroader:
I made an RPM, a digital speed indicator, and some more to my car a long time ago. It ran for 10 years without any problem.
Connect to the low voltage side of the ignition coil and use an optocoupler and a threshold circuitry to catch/deliver the pulse signal You will feed to Your controller. Then measuring the time between pulses and adjusting to the number of cylinders etc. is no big deal. Some math.

If I understand correctly the optocoupler and a threshold circuitry creates a new pulse signal with just 1 clean pulse per ugly/unfiltered pulse? If so I’ll keep this in mind in case I can’t come up with a working “software-solution”.

PaulRB:
You need to have a “dead time” after each interrupt, so that further interrupts within that dead time do not increment the counter. It’s a lot like debouncing a button. You need to calculate how long that dead time should be by knowing the maximum revs the engine should ever reach, calculating the interval between interrupts, in microseconds, at that maximum revs, and choosing a time a little less than that. Then, in the ISR, you can check if enough time has passed since the counter was last incremented. If it has, increment the counter and calculate the earliest time for the next increment. If not, don’t increment the counter.

Can’t I simply calculate the minium pulse-duration-time and add that time as delay in the ISR? When calculating the rpm I just add the number of rev’s*delay time to the passed time.

JCA79B:
Did you try:

attachInterrupt(0,isr,FALLING);

?
Did you try using nointerrupts() , interrupts() , instead of detaching and reattaching?
noInterrupts() - Arduino Reference

I have tried FALLING instead of RISING, sadly it made no difference. If have not looked into nointerrupts() , interrupts(). I may give it a try if adding a delay to the ISR doesn’t work.

Railroader:
Conditioning the signal Before feeding it to the controller looks neccessary to me. To “filter out” a valid signal surronded by ghost signals, like a rain of signals, interrupts, can’t give a proper reading.

Counting ignition pulses versus the rate of update gives a conflict. How many pulses are needed to give a reasonable resolution? ± 10 RPM at some thousand RPM? Say 3600 RPM and 4 cylinders. That is 60 revs per second, 2 sparks per rev, 120 pulses that will be multiplied by 30 to show 3600 RPM.

It doesn’t have to be very accurate to me 12xx rpm instead of 125x rpm is also acceptable.

PaulRB:

  if (rpm>7000){
rpm=7000;

}

if (rpm=<7000){
display.showNumberDec(rpm, false);
}
else{
display.setSegments(SEG_ERR); //Show error message if rpm > 7000
}



This is never going to show "Err", because rpm will never be over 7000.

You are correct.

 if (rpm<7000){
    display.showNumberDec(rpm, false);
  }

Should fix it am I right? I think I will just delete that part of the code. It is useless when the code functions properly. The engine will never reach 7000 rpm.

Slumpert:
Might be as simple as adding

delayMicroseconds(100) within the isr to keep it from retriggering on the after-pulse.

You will know how many times you paused in the isr so you could easily add that back into your “time” for processing.

This seems like the most simple solution I’ll try this first. I just need to calculate the optimal delay time.
calculate max rpm per second
6000/60=100

calculate max number of pulses per second
100*8=800

calculate min time between start of first pulse and next pulse
1000/800=1.25ms

I’ll try a delay of 125 μs first.

bluejets:
Some filtering circuits you might be able to adapt here…

SportDevices. CDI Programmable Digital Ignition.

I’ll look in to this in case I can’t come up with a working “software-solution”.

The rotor in the distributor rotates at half crankshaft speed , as each cylinder on fires once per 2 revs of the crank. With normal coil type ignition the coil rings with the points capacitor and cant really do anything with the waveform . I would be tempted to try using an opto isolators on the input signal and filter it’s output . alternatively a proximity sensor on the crank pulley nuts would give a better signal.

There is only so far you can go with software signal conditioning and you May have to mod the hardware .

sjirafje: A solution which does not require additional components/circuitry is preferred.

This does not make sense ......... :(

Google "otto cycle" to see how your 4-cycle engine operates. You my be surprised to learn the cam shaft also operates at 1/2 the RPM of your motor. Usually the distributor is driven by the cam shaft using a bevel gear giving a 1:1 gear ratio.

Paul

A four stroke cylinder ignites every second revolution. That is 1/2 per rev. Having 8 cylinders gives 4 ignitions per rev. Yes, I recomend some filtering only picking up the unic top of the pulse. A little bit of boosting the optodiode gave me enough of digital pulse for a 4 MHz Z80 sydtem. Sorry I can't provide the simple circuitry. I made it 30 years ago.

Can't I simply calculate the minium pulse-duration-time and add that time as delay in the ISR? When calculating the rpm I just add the number of rev's*delay time to the passed time.

It's not good practice to put delays in ISRs, they should be as short to execute as possible. Plus, you would be wasting useful processing time. It's only a couple of extra lines of code to do it properly, in the way I suggested.

calculate min time between start of first pulse and next pulse 1000/800=1.25ms

I'll try a delay of 125 μs first.

So only 10%? Or was that a typo and should have been 1250us?

According to the oscilloscope picture it looks like the duration of one pulse is some 12-13 mS. 10 mS per Div...

Railroader: it looks like the duration of one pulse is some 12-13 ms

I agree, the "dead time" should be around 10ms, not 1.25ms.

The peak voltage looks like some 40 volt, 2 Div of 20 Volt/Div. It quickly drops to some 20 volt for the rest of the pulse. I would suggest a some 30 volt Zener diode in serie and a current liniting resistor Before an opto coupler. As the peak, higher voltage than the zener voltage is short I think that maximum, or 50 to 100% higher, current could be alowed into the opto if needed to trigger the interrupt. A quite easy and small filter I think.

a schmidtt trigger is your answer for cleaning up the signal. There is a company that makes what you need but they also have schematics for the device.

So here it is https://github.com/autosportlabs/CoilX/blob/master/hardware/CoilX_sch.png