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

OP seams to be busy. Last time responding was more than a week ago. In the mean time helpers can add more arguments drowning OP in more or less useful possibilities.

and your point is?

Wait for a reponse from OP. Nobody wants lots of different suggestions. I have asked for help myself, got plenty of different suggestions and there was just no time, no energy, to follow up all those different ideas.

Unfortunately the car doesn't run at the moment. Whet I get it fixed (may take a few months), the first thing I'm going to try is to use a capacitor to try and clean/smoothen the signal that's coming from the voltage divider and see if that works. I've ordered an assortment of ceramic capacitors to experiment with. Next suggestion i'm going to try is to use a zener diode (and perhaps a octo coupler).

Consider a circuit like this:

to isolate the logic from the mess off the coil; a diode, couple of resistors, a zener and an opto-coupler (and maybe a small 0.1uF cap on the logic-side VCC pin of the opto...)

@Blackfin
That looks prity much like the curcuitry I once made, tapping the same signal, maybe the posituve side, and that project ran for 10 years without a hickup.
@OP
Adopt this idea. It's really good.

In the interest of giving credit where it's due, I cribbed the image from here:

Looked sound to me.

I'm going to order the parts to build that circuit. I'm assuming the H11L1MS also accepts 5V instead of 3.3V? Can I use ordinary 0.25W resistors?

Check that up! Don't push 5 volts into any 3.3 volt device is my advice. It might work sometimes but fail other times or fail after some time of running.

I've built the suggested circuit using this diagram (found here: Ignition Coil Interface · Issue #2 · seanauff/classic-car-sensor-interface · GitHub):

My H11L1MS indeed accepts 5V and it outputs a nice clean signal for the Arduino:

I'm not sure if I'm happy with the accuracy though:

Hz		  rpm 		rpm Arduino		difference %	difference
42,756	 641		 741				 15,6				100
43,36	  650		 769				 18,3			   119
43,234	 649		 710				 9,4			    61
43,323	 650		 656				 0,9			    6
85,513	 1283		1396				8,8			    113
86,29	  1294		1510				16,7			   216
86,2	   1293		1453				12,4			   160
85,685	 1285		1453				13,1			   168
85,513	 1283		1425				11,1			   142
85		  1275		1396				9,5			    121
85,255	 1279		1368				7			      89
123,239	1849		1966				6,3			    117
124,626	1869		1881				0,6			    12
123,639	1855		1938				4,5			    83
120,12	 1802		1897				5,3			    95
121,047	1816		1955				7,7			    139
121,167	1818		1824				0,3			    6
120,927	1814		1966				8,4			    152
121,407	1821		1909				4,8			    88
134,596	2019		2166				7,3			    147
134,596	2019		2052				1,6			    33
136,003	2040		2070				1,5			    30
143,564	2153		2337				8,5			    184
152,347	2285		2394				4,8			    109
158,572	2379		2308				3			      71
158,887	2383		2365				0,8			    18
103,3	  1550		1738			   12,1			   188

The first column is measurement from the oscilloscope, the 2nd calculated rpm from oscilloscope, 3rd rpm calculated by adruino.

I'm now calculating the rpm using rev*(60000/time)/(C/2).
rev = number of pulses counted
time = measuring time (ms)
c = number of cilinders

Is there a way to improve the accuracy? I am aware that this method of calculation makes that the value the Arduino displays always lags slightly behind compared to the actual rpm.

Perhaps it would be better to measure the time between pulses?

Well done! That is a success. There is some error but surely we can trim that down.
What circuitry are You using to transform the signal to Arduino levels?

Is there a way to improve the accuracy?...
Perhaps it would be better to measure the time between pulses?

There are several approaches to increase accuracy and reduce lag, but ultimately it will be a trade-off between the two. Right now we don't know what's causing the inaccuracy. It could be C coding errors, who knows? Post your current code.

Sounds from the formula you mentioned that you are counting pulses over a fixed period. Assuming no coding errors, the accuracy of that method is limited by the number of pulses counted. The smaller the count, the lower the accuracy. Other methods include timing a single pulse cycle, timing a fixed number of pulse cycles, measuring width of single pulse, total width of a fixed number of pulses...

All verry interesting and helpfull. As an engineer the output from the distributor to trigger the coil reies on the collapse of the stored current in the capacitor which bridges the points. That voltave can get quite high depending on the condition of the points, the condenser, and also the quality of the earth in the distributor baseplate. From experience the last one has caused more problems than anything else. So. There were a companies many years ago which proudced devices which got rid of the points/Capacitor set up andd replaced them with an opto coupler or hall sender; which was the system eventually adopted by most manufacturers.
Trying to clean up the output is always a problem and many tachometers adopted a different system. Mine has a coil which is wound round the No1 H.T lead, the induced voltage is then read, amplified and then used to drive the tacho.
I have a similar problem which I am working on and I am just about to post a circuit for some advice and consideration.
I will be watching for further input.

Railroader:
Well done! That is a success. There is some error but surely we can trim that down.
What circuitry are You using to transform the signal to Arduino levels?

bitterend:
All verry interesting and helpfull. As an engineer the output from the distributor to trigger the coil reies on the collapse of the stored current in the capacitor which bridges the points. That voltave can get quite high depending on the condition of the points, the condenser, and also the quality of the earth in the distributor baseplate. From experience the last one has caused more problems than anything else. So. There were a companies many years ago which proudced devices which got rid of the points/Capacitor set up andd replaced them with an opto coupler or hall sender; which was the system eventually adopted by most manufacturers.
Trying to clean up the output is always a problem and many tachometers adopted a different system. Mine has a coil which is wound round the No1 H.T lead, the induced voltage is then read, amplified and then used to drive the tacho.
I have a similar problem which I am working on and I am just about to post a circuit for some advice and consideration.
I will be watching for further input.

The circuitry I'm using is shown here:


The only difference is that instead of using 3.3v I'm using 5V so I can use the same power source (a usb-powerbank for now) for the display, the arduino and the optocoupler. This produces a clean square wave as can be seen on the oscilloscope (see photo or video in my previous post). For now I stuck the components on a breadboard. I plan on soldering it when I'm happy with the accuracy.

PaulRB:
There are several approaches to increase accuracy and reduce lag, but ultimately it will be a trade-off between the two. Right now we don't know what's causing the inaccuracy. It could be C coding errors, who knows? Post your current code.

Sounds from the formula you mentioned that you are counting pulses over a fixed period. Assuming no coding errors, the accuracy of that method is limited by the number of pulses counted. The smaller the count, the lower the accuracy. Other methods include timing a single pulse cycle, timing a fixed number of pulse cycles, measuring width of single pulse, total width of a fixed number of pulses...

Yes the arduino counts pulses during the "display update interval" (currently set to 500ms). The arduino also calculates the actual measuring time.
This is my current code

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

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

//     A
//    ---
// F |   | B
//    -G-
// E |   | C
//    ---
//     D

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

const uint8_t SEG_HOI[] = {
  SEG_G,           // -
  SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,   // H
  SEG_E | SEG_F,  //I
  SEG_G            // -
  };

const uint8_t SEG_OFF[] = {
  0x0,           // uit
  SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,   // O
  SEG_A | SEG_E | SEG_F | SEG_G,  //F
  SEG_A | SEG_E | SEG_F | SEG_G,  //F
  };

float rev=0;      //
int c=8;          //number of cylinders
int rpm;          //
int oldtime=0;    //
int time;         //
int updateInterval = 500;   //display update interval (ms), the higher this value, the slower the screen updates, but RPM is calculated more precise

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

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

void loop() {
  delay(updateInterval);        //wait display update interval
  detachInterrupt(0);           //detaches the interrupt
  time=millis()-oldtime;        //finds the time 
  rpm=rev*(60000/time)/(c/2);   //calculate rpm (number of pulses * (amount of measuring periods in a minute) / (number of cylinders/2)
  oldtime=millis();             //saves the current time
  rev=0;                        //resets number of pulses counted

  if (rpm<0){
    display.setSegments(SEG_ERR);
  }
  if (rpm==0){
    display.setSegments(SEG_OFF);
  }  
  if (rpm>9999){
    display.setSegments(SEG_ERR);
  }
  if (rpm>0){
    display.showNumberDec(rpm, false);
  }

  attachInterrupt(0,isr,FALLING);
}

Read #31 again. The resolution is poor counting pulses. Do some math. RPM stands for revolutions per minute. Your measureing time is only half a second. That is like RPM divided by 60*2, 120.Your readings ought to jump in steps of 120/4 (8 cylinders, i pulse per 2 revolution per cylinder == 4 pulses per one revolution).

float rev=0;

This variable should not be a float, it only ever holds positive, integer numbers, so I suggest "unsigned int" or "unsigned long". Floats are slow and should be avoided where possible. I don't see any need for them in your code. Also, because it is used by the interrupt routine and other functions, it should be declared "volatile".

int oldtime=0;
int time;

Any variable used to hold or calculate values using millis() should be "unsigned long", otherwise overflows can occurr with strange results.

rpm=rev*(60000/time)/(c/2);

When dividing two integers, C will perform integer division, which can cause loss of precision. To minimise that, perform multiplications first and divisions after:

rpm=rev*60000*2/time/c;
time=millis()-oldtime;

Your idea was to count the pulses over "updatePeriod", and you enable/disable interrupts to avoid counting any pulses while your code is calculating the rpm and updating the display. But the line above will include the time taken to update the display, which could be several tens of milliseconds, so this will introduce some inaccuracy into your rpm calculation.

At 42.7Hz, you would expect to detect either 21 or 22 pulses in your 500ms measurement period. That would give an error of 30rpm. But your results are 100rpm too high, indicating 3 or 4 extra detections. so I wonder if your circuit is still detecting some pulses multiple times. Try printing the value of "revs".

I don't think these suggestions will fix your problems, but they are important principles you should be aware of.

Quick question, does using "RISING" instead of "FALLING" for the interrupt make any difference in my case?

Railroader:
Read #31 again. The resolution is poor counting pulses. Do some math. RPM stands for revolutions per minute. Your measureing time is only half a second. That is like RPM divided by 60*2, 120.Your readings ought to jump in steps of 120/4 (8 cylinders, i pulse per 2 revolution per cylinder == 4 pulses per one revolution).

I'm not sure if I understand what you're saying. At idle the engine will run at 500-750 rpm. Let's say that minimum rpm is 450. At 450 rpm the pulse frequency is 30 Hz. With a display update interval of 500ms the arduino should count 15 pulses. Or am I mistaken?

If the arduino counts 14 pulses instead of 15 pulses it should calculate 420 rpm (30 rpm or 6.7% off), if 16 pulses were counted it should calculate 480 rpm (30 rpm or 6.7% off). So it should calculate rpm with at least 93% accuracy at low rpm (and 99.8% at 7500rpm) if I'm not mistaken.

PaulRB:

float rev=0;

This variable should not be a float, it only ever holds positive, integer numbers, so I suggest "unsigned int" or "unsigned long". Floats are slow and should be avoided where possible. I don't see any need for them in your code. Also, because it is used by the interrupt routine and other functions, it should be declared "volatile".

The float variable was a leftover from a previous version which I forgot to change. Thanks for pointing it out, but what do you mean by declaring it volatile?

int oldtime=0;

int time;



Any variable used to hold or calculate values using millis() should be "unsigned long", otherwise overflows can occurr with strange results.

I've changed the datatypes into unsigned long. btw Is there a downside to using unsigned long instead of int? I'm asking because I'm not sure if it will ever overflow as millis() would overflow after around 50 days (micros() after 70 minutes) but the engine wil not run for more than 6 hours straight (after which the arduino will also be turned off, resetting millis()).

rpm=rev*(60000/time)/(c/2);

When dividing two integers, C will perform integer division, which can cause loss of precision. To minimise that, perform multiplications first and divisions after:

rpm=rev*60000*2/time/c;

Thanks, I was not aware of this.

time=millis()-oldtime;

Your idea was to count the pulses over "updatePeriod", and you enable/disable interrupts to avoid counting any pulses while your code is calculating the rpm and updating the display. But the line above will include the time taken to update the display, which could be several tens of milliseconds, so this will introduce some inaccuracy into your rpm calculation.

Would it help to move this line

oldtime=millis();

to the end of the loop, right before the interrupt gets reattached?

At 42.7Hz, you would expect to detect either 21 or 22 pulses in your 500ms measurement period. That would give an error of 30rpm. But your results are 100rpm too high, indicating 3 or 4 extra detections. so I wonder if your circuit is still detecting some pulses multiple times. Try printing the value of "revs".

I've previously disabled the calculation and let it display time(=millis()-oldtime)This way I noticed it takes about 21 ms to complete the loop. I then set the update interval to 979ms and let it display rev (= Hz. ) And it was almost spot on with the oscilloscope. So I don't think there is a problem with the counting of the pulses.

I don't think these suggestions will fix your problems, but they are important principles you should be aware of.

The rpm calculated by the arduino cannot be compared directly to the value displayed by the oscilloscope as the measurement of the arduino is an average of the previous 500ms where the oscilloscope calculates the frequency by measuring the length of the last pulse. I will test the suggested changes first, perhaps the improvement already makes the accuracy acceptable.

sjirafje:
Quick question, does using "RISING" instead of "FALLING" for the interrupt make any difference in my case?

An interrupt should be used for the non-normal situation. What is the normal, longest time, for the signal, 5 volts or zero volts? The answer will tell you what should be the interrupt.

Paul

Would it help to move this line
Code: [Select]
oldtime=millis();
to the end of the loop, right before the interrupt gets reattached?

Yes, but why not forget time and oldtime and simply write

rpm=rev*60000*2/updateInterval/c;