I'm interested in creating a datalogging tachometer for a carburated V8 engine. This engine uses a HEI (High Energy Ignition) distibutor) not "points". For reference its a naturally aspirated chevy 454 thats in a 1979 one ton chevy (K30). In addition I do not pretend to be particularly knowledgeable about automotive technology or electronics. I have found similar posts on this forum and when it comes to counting pulses I am not (at this point) looking to put an inductive clamp on a spark plug wire, I am looking to count pulses from the tachometer output of the distributor cap.
Is there an off the shelf opto-coupler (or any other isolation circuit) that is suitable for my needs. I would prefer not to build this circuit, however its not out of the question. It looks to me that the off the shelf optocouplers expect the 3v signal from the microprocessor as input... where it is my intent to read the 0-30vdc from the distributor and send the 0-3vdc to the microprocessor.
My signal has some real spikes as I suppose would be expected. I was able to borrow an oscilloscope and one photo shows unfiltered and the other shows the signal filtered with a first order low pass filter with R=12k and C=9.43uF.
The reference used thus far has been here:
Again, I primarily want to know if there's an off the shelf isolator or if I need to make it.
The automotive electrical environment is extremely harsh. You can expect spikes of 125V or higher to be induced in unshielded wires, and if the 12V system is used for power, your circuitry should be protected by TVS diodes and excellent filtering.
Thanks for the response. I'll look into TVS diodes in the future but right now I'm focusing on getting the tachometer to count pulses. I think that the opto-coupler circuit itself effectively separates the mcu from the automotive electrical.
I did find a project online which outlines a opto-coupler circuit that could be built. The project is here:
Instead of building the opto-coupler circuit I found one online that was a complete breakout board, the specs to me were vague but it is rated a 24v unit and it looks to me that the 24v is the trigger. I used my rc filter and it works out well enough. Next step is to connect to the mcu and display the results.
Thanks gilshultz. Yes this is very relevant to the long term. I've read through the documents I will keep them around as reference...
As I get past prototyping I certainly want to incorporate best practices. Though not relevant to this thread the vehicle wiring needs a complete overhaul and it is something that I hope to do in the next year, so even best practices for automotive are topics that I've started to do some reading on. I have very little automotive experience and I have come to realize much as is pointed out in the reading that its a harsh environment... and not just for electronics but everything is subject to weather, extreme temperature, vibration, and other sources of damage.
I hope to post rpm soon, but in so many ways my setup right now is nothing more than prototyping... I'm still using breadboards and such... my electronics have thus far only been setup in the engine bay for temporary use.
What do those pulses represent? Is there one pulse per cam shaft rotation or is there one pulse per cylinder firing? In other words, one pulse for every two engine revolutions or eight pulses for every two engine revolutions?
its an 8 cylinder engine and its four stroke. each pulse represents a spark plug firing.
since its four stroke the crankshaft will go around two revolutions once all cylinders fire or more simply there are four sparks per revolution.
you can see that these pics show the pulses at about 20ms, whats not shown is that I used an inductive clamp meter on a spark plug and independently measured engine speeds. in these photos engine was warm and at idle... and corresponded to the scope measurements.
(60 sec/min)/((.02sec/spark)*(4spark/rev)) = 750 rev / min
I will use millis() and will count the time between pulses. The example project I referenced above seems to work that way. At this point I have not written the code out so whether I stick with the reference example or not... its just that I haven't got that far yet, but take a look at the example and once you open the file search for "rpm"
When close timing counts, work with micros() as millis() are +/- 1 ms plus clock source error.
Arduino micros() has a 4 us grain, +/- is the clock source error.
If you put a coil across the tach output and read field with a Hall switch, that might sub for an opto.
If you could read a gear on the shaft, buy or make a gear tooth counter and get far more detailed data without stretching Arduino.
Would solid within 50 micros timing on spark suture needs?
I had a few days off for the holidays and expected to be further along. I got a bit of a cold so that changed my plans... I worked a little on the background but at this point I've got no prototype in terms of counting pulses with an arduino but... I can use the opto-coupler bought from amazon and take my pulses right from the distributor cap and generate low voltage pulses that are arduino readable. I'm using a ESP32 so its 3.3V pulses that I want.
I considered using a coil and this is how I presently read rpm. I've got a dial back timing light and its got an inductive clamp that goes around a spark plug wire. I am aware of this method for sure but my preferred is to read from the distributor cap so that I do not have to mount the coil on the clamp permanently... for a permanent installation it seems to me that it'll be easier to mount the opto-coupler somewhere.
If 750 rpm is .020s then .020+/-.001 is not very good error. If I can get this +/-.001 down to +/-.00005... yes I would be happy with that error. I don't need precision but I'd like error to be better than 5%
As to whether I'm interested in using another method to measure RPM... I understand and appreciate the comment but I'm trying as hard as possible to do as little as possible (mechanically at least). Vehicles are a tough environment and this in particular is a plow truck (well I'm looking to put a dump body on it too) so it is a hard ride (stiff/bumpy)... Even though I could get a better measurements from another method I'm starting with this since I won't have to install any other pieces or modify anything to the existing parts.
If all you do is switch from using millis() to micros(), you get more precise and accurate at the same time. 1000 micros = 1 milli when you plug the numbers in, just add 3 zeroes.
If you want to control spark advance, would reading spark plug surges make sense?
Does the crankshaft have a belt pully on the engine front? Like for the water pump?
Here's partial code for a working tachometer. There are some parameters you can set.
It just uses pulsein, so that is microseconds. No need for interrupts or anything fancy.
It will probably work as-is if you add you own section to do whatever you like with the result -> display, serial, whatever...
I use it on a Mega2560, so check the input pin is suitable for you.
There are some calcs done in "setup" to avoid doing them repeatedly in "loop"
I know the forum is about teaching, but I'd like to pass on this almost complete example.
Some would say "but pulsein is blocking" and I would say how quickly do you require the RPM updates? I have a stack of other code in my working sketch for a TFT display and other things, and RPM is updated on the screen almost faster than you can read it.
// Automotive tachometer
//========================================================================
//-------------------------- Set These Manually --------------------------
//========================================================================
int RPM_redline = 8000;
int RPMyellowline = 6000;
int cylinders = 4;
int minimum_RPM = 500;
// Kludge factor to allow for differing
// crystals and similar inconsistancies
float Kludge_Factor = 0.994;
//========================================================================
const int RPM_Input_Pin = 6; // RPM frequency INPUT pin
int RPM;
unsigned long hightime, lowtime, pulsein_timeout, period_min;
float freq, period, RPM_f, RPM_constant;
int maximumRPM = RPM_redline * 1.2;
int lowRPM = RPM_redline / 3;
void setup()
{
pinMode(RPM_Input_Pin, INPUT);
// -------------------------------------------------------
// Maximum time for pulseIn to wait in microseconds
// Use the period of the lowest expected frequency
// based on cylinders and minimum_RPM, and double it
pulsein_timeout = 1000000.0 / ((float)minimum_RPM * (float)cylinders / 120.0) * 2.0;
// -------------------------------------------------------
// -------------------------------------------------------
// Calculate the minimum period in microseconds
// values beyond this would be considered noise or an error
// this helps to prevent divide by zero errors
// based on cylinders and maximumRPM, and halve it
period_min = 1000000.0 / ((float)maximumRPM * (float)cylinders / 120.0) / 2.0;
// -------------------------------------------------------
// -------------------------------------------------------
// Calculate this constant up front to avoid
// doing so many divides every loop
RPM_constant = 120000000 / (float)cylinders * Kludge_Factor;
//based on:
//freq = 1000000.0 / (float)period;
//RPM = freq / (float)cylinders * 120.0;
//RPM = 1000000.0 / (float)period / (float)cylinders * 120.0
// -------------------------------------------------------
} // End void setup
void loop()
{
// Read the RPM signal
hightime = pulseIn(RPM_Input_Pin, HIGH, pulsein_timeout);
lowtime = pulseIn(RPM_Input_Pin, LOW, pulsein_timeout);
period = hightime + lowtime;
// for testing
//period = random(period_min, pulsein_timeout);
// prevent overflows or divide by zero
if (period > period_min)
{
RPM = int(RPM_constant / (float)period);
}
else
{
RPM = 0;
}
RPM = constrain(RPM, 0, maximumRPM);
// Optional
// Round RPM to nearest 100's or 10's
RPM_f = (float)RPM;
if (RPM >= lowRPM)
{
RPM = int((RPM_f + 50.0) * 0.01) * 100;
}
else
{
RPM = int((RPM_f + 5.0) * 0.1) * 10;
}
// Do something with your RPM value here
} // End void loop
Using floats on AVR-duinos like the Mega2560 slows the math down.
I learned math paper & pencil so to me, decimal is a dot to hold my place.
You keep time in millionths of a second, micros. An unsigned long can time 4294.967296 seconds as 4294967296 micros. Period should be in micros and RPM is 60000000 micros / period micros which brings up a difference between pure math and computer math...
Computers operate on binary values. With integers you can lose places in divides though the remainder can be saved and precision restored. But if you work in 6 place units and only need 3 then you can lose 3 and be fine, But save the divides for last operations and avoid making decimal factors.
If Kludge_Factor = 994 and the Kludge Math went * factor / 1000, would that work?
I like microseconds. C rounds to 300 m per, a relatable distance!
Work in small units and then display float human units calculated at the end, like turning pi to decimals in the last = of a trig solution. It was pure up to then.
Yes I agree with you and I have done some slight improvements over what I had when I first started on that project. If I just wanted to accurately gather the RPM data at a high rate, the faster maths and similar improvements would be the way to go.
The thing is I am using a very large font on a 480x320 TFT and I'm already at the point where I'll have to add a delay or average a few readings to allow the display to be readable if I take out other features or speed up the code - in fact the large font probably provides a self-limiting effect.
That's with a bar graph, sending out the RPM value via serial and other stuff...
You could put the display on a timer and only update if the data changed and say 1/2 second has gone by since last update.
With non-blocking code, the sensing and processing runs as one task that always updates the data and the display code runs separately as another function in loop(). Neither needs to accommodate the other into its own code except to share a global variable that one (sense and process) write and the other (display) reads.
This kind of code allows adding functions as stand-alone tasks.
Hi @GoForSmoke
Just letting you know I take onboard the things you've pointed out, and I may change my tacho and speedo sketches to use that program flow you point out. I do use that in other sketches, so I am familiar with what you mean.
For the tacho and speedo I had just used a top-down/linear flow since the display and large font has governed the timing. But I will explore, and probably look to improve the maths too.
Non-blocking code goes at least to 70's EE's.
In the 80's it was called Main Loop programming (I caught that wave) so when I tried the Arduino IDE and saw setup() and loop(), I smiled.
I think a good name is event-driven where change in time is an event.
A change to a global variable by one function is an event to another.
A pin change is an event. Serial.available > 0 .. event.
Is your display able to draw a dial needle? Eyes see motion at 24 frames per second. If the display can erase and redraw every 40 ms, that is 25 FPS. If the display can change faster the illusion will be smoother for the fills.