Sensing the signal and put delay based on RPM and then OUTPUT

Hi there. im a beginner and i’m stuck in a problem. im making a Advance Retard ignition system for my bike. My bike engine produce a Signal Pulse every single RPM which goes through the microcontroller circuitry and further it’s fed to the Ignition which makes spark in engine. the trickiest part here is the microcontroller working. The microcontroller sense the Engine Pulse Signal and put a delay of 5mS and then gives output to the ignition coil <this function work the lower rpm’s(1200- 2500rpm) but when the engine RPM increase above 2500 the microcontroller sense the Input Signal and put delay of 0mS, in simple words the ucontroller fed the power to the ignition coil as soon as it sense the engine signal pulse(remember this only happens when engine rpm is above 2500). i want to make this system by using Arduino Mega just by assigning my own values of delay for particualr range of RPM’s. my engine idle at 1200RPM and MAX RPM is 13000.
for example
if engine rpm is
1000 - 2000 put delay 5mS
2000 - 2500 put delay 4ms
… …
.
… .
. …
5000 - 13000 put delay 0mS

if have done with the coding for RPM counter to get precise RPM. im a beginner so i dont know how to assign the values . it would be appreciated if someone gives me the solution

Please post your code following the advice given in the link below

I read this question some time ago. Multiple posting?

/*
Tachometer using micros

On this sketch we are going to measure the period between pulses using the micros() function to get the RPM
(Revolutions Per Minute) from a sensor on pin 2.
This way of measuring RPM makes it accurate, responsive and versatile. No matter how fast or slow the loop is
running, the reading accuracy is not going to be affected. Although, the faster you run the loop, the more amount
of readings you are going to be able to display every second.

It’s coded in a way that the micros rollover doesn’t create glitches every 71 minutes, so it can run forever
without problems.

We use an interrupt for the input so you have to choose pin 2 or 3 (for Arduino Uno/nano). In this example we
use pin 2.

*/

///////////////
// Calibration:
///////////////

const byte PulsesPerRevolution = 2; // Set how many pulses there are on each revolution. Default: 2.

// If the period between pulses is too high, or even if the pulses stopped, then we would get stuck showing the
// last value instead of a 0. Because of this we are going to set a limit for the maximum period allowed.
// If the period is above this value, the RPM will show as 0.
// The higher the set value, the longer lag/delay will have to sense that pulses stopped, but it will allow readings
// at very low RPM.
// Setting a low value is going to allow the detection of stop situations faster, but it will prevent having low RPM readings.
// The unit is in microseconds.
const unsigned long ZeroTimeout = 100000; // For high response time, a good value would be 100000.
// For reading very low RPM, a good value would be 300000.

// Calibration for smoothing RPM:
const byte numReadings = 2; // Number of samples for smoothing. The higher, the more smoothing, but it’s going to
// react slower to changes. 1 = no smoothing. Default: 2.

/////////////
// Variables:
/////////////

volatile unsigned long LastTimeWeMeasured; // Stores the last time we measured a pulse so we can calculate the period.
volatile unsigned long PeriodBetweenPulses = ZeroTimeout+1000; // Stores the period between pulses in microseconds.
// It has a big number so it doesn’t start with 0 which would be interpreted as a high frequency.
volatile unsigned long PeriodAverage = ZeroTimeout+1000; // Stores the period between pulses in microseconds in total, if we are taking multiple pulses.
// It has a big number so it doesn’t start with 0 which would be interpreted as a high frequency.
unsigned long FrequencyRaw; // Calculated frequency, based on the period. This has a lot of extra decimals without the decimal point.
unsigned long FrequencyReal; // Frequency without decimals.
unsigned long RPM; // Raw RPM without any processing.
unsigned int PulseCounter = 1; // Counts the amount of pulse readings we took so we can average multiple pulses before calculating the period.

unsigned long PeriodSum; // Stores the summation of all the periods to do the average.

unsigned long LastTimeCycleMeasure = LastTimeWeMeasured; // Stores the last time we measure a pulse in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.
unsigned long CurrentMicros = micros(); // Stores the micros in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.

// We get the RPM by measuring the time between 2 or more pulses so the following will set how many pulses to
// take before calculating the RPM. 1 would be the minimum giving a result every pulse, which would feel very responsive
// even at very low speeds but also is going to be less accurate at higher speeds.
// With a value around 10 you will get a very accurate result at high speeds, but readings at lower speeds are going to be
// farther from eachother making it less “real time” at those speeds.
// There’s a function that will set the value depending on the speed so this is done automatically.
unsigned int AmountOfReadings = 1;

unsigned int ZeroDebouncingExtra; // Stores the extra value added to the ZeroTimeout to debounce it.
// The ZeroTimeout needs debouncing so when the value is close to the threshold it
// doesn’t jump from 0 to the value. This extra value changes the threshold a little
// when we show a 0.

// Variables for smoothing tachometer:
unsigned long readings[numReadings]; // The input.
unsigned long readIndex; // The index of the current reading.
unsigned long total; // The running total.
unsigned long average; // The RPM value after applying the smoothing.

void setup() // Start of setup:
{

Serial.begin(9600); // Begin serial communication.
attachInterrupt(digitalPinToInterrupt(2), Pulse_Event, RISING); // Enable interruption pin 2 when going from LOW to HIGH.

delay(1000); // We sometimes take several readings of the period to average. Since we don’t have any readings
// stored we need a high enough value in micros() so if divided is not going to give negative values.
// The delay allows the micros() to be high enough for the first few cycles.

} // End of setup.

void loop() // Start of loop:
{

// The following is going to store the two values that might change in the middle of the cycle.
// We are going to do math and functions with those values and they can create glitches if they change in the
// middle of the cycle.
LastTimeCycleMeasure = LastTimeWeMeasured; // Store the LastTimeWeMeasured in a variable.
CurrentMicros = micros(); // Store the micros() in a variable.

// CurrentMicros should always be higher than LastTimeWeMeasured, but in rare occasions that’s not true.
// I’m not sure why this happens, but my solution is to compare both and if CurrentMicros is lower than
// LastTimeCycleMeasure I set it as the CurrentMicros.
// The need of fixing this is that we later use this information to see if pulses stopped.
if(CurrentMicros < LastTimeCycleMeasure)
{
LastTimeCycleMeasure = CurrentMicros;
}

// Calculate the frequency:
FrequencyRaw = 10000000000 / PeriodAverage; // Calculate the frequency using the period between pulses.

// Detect if pulses stopped or frequency is too low, so we can show 0 Frequency:
if(PeriodBetweenPulses > ZeroTimeout - ZeroDebouncingExtra || CurrentMicros - LastTimeCycleMeasure > ZeroTimeout - ZeroDebouncingExtra)
{ // If the pulses are too far apart that we reached the timeout for zero:
FrequencyRaw = 0; // Set frequency as 0.
ZeroDebouncingExtra = 2000; // Change the threshold a little so it doesn’t bounce.
}
else
{
ZeroDebouncingExtra = 0; // Reset the threshold to the normal value so it doesn’t bounce.
}

FrequencyReal = FrequencyRaw / 10000; // Get frequency without decimals.
// This is not used to calculate RPM but we remove the decimals just in case
// you want to print it.

int ignitiondel= map(FrequencyReal,0,217,5000,0);

// Calculate the RPM:
RPM = FrequencyRaw * 2 / PulsesPerRevolution * 60; // Frequency divided by amount of pulses per revolution multiply by
// 60 seconds to get minutes.
RPM = RPM / 10000; // Remove the decimals.

// Smoothing RPM:
total = total - readings[readIndex]; // Advance to the next position in the array.
readings[readIndex] = RPM; // Takes the value that we are going to smooth.
total = total + readings[readIndex]; // Add the reading to the total.
readIndex = readIndex + 1; // Advance to the next position in the array.

if (readIndex >= numReadings) // If we’re at the end of the array:
{
readIndex = 0; // Reset array index.
}

// Calculate the average:
average = total / numReadings; // The average value it’s the smoothed result.

// Print information on the serial monitor:
// Comment this section if you have a display and you don’t need to monitor the values on the serial monitor.
// This is because disabling this section would make the loop run faster.
Serial.print("Period: “);
Serial.print(PeriodBetweenPulses);
Serial.print(”\tReadings: “);
Serial.print(AmountOfReadings);
Serial.print(”\tFrequency: “);
Serial.print(FrequencyReal);
Serial.print(”\tRPM: “);
Serial.print(RPM);
Serial.print(”\tTachometer: “);
Serial.print(average);
Serial.print(”\tignitiondelay: ");
Serial.println(ignitiondel);

} // End of loop.

void Pulse_Event() // The interrupt runs this to calculate the period between pulses:
{

PeriodBetweenPulses = micros() - LastTimeWeMeasured; // Current “micros” minus the old “micros” when the last pulse happens.
// This will result with the period (microseconds) between both pulses.
// The way is made, the overflow of the “micros” is not going to cause any issue.

LastTimeWeMeasured = micros(); // Stores the current micros so the next time we have a pulse we would have something to compare with.

if(PulseCounter >= AmountOfReadings) // If counter for amount of readings reach the set limit:
{
PeriodAverage = PeriodSum / AmountOfReadings; // Calculate the final period dividing the sum of all readings by the
// amount of readings to get the average.
PulseCounter = 1; // Reset the counter to start over. The reset value is 1 because its the minimum setting allowed (1 reading).
PeriodSum = PeriodBetweenPulses; // Reset PeriodSum to start a new averaging operation.

// Change the amount of readings depending on the period between pulses.
// To be very responsive, ideally we should read every pulse. The problem is that at higher speeds the period gets
// too low decreasing the accuracy. To get more accurate readings at higher speeds we should get multiple pulses and
// average the period, but if we do that at lower speeds then we would have readings too far apart (laggy or sluggish).
// To have both advantages at different speeds, we will change the amount of readings depending on the period between pulses.
// Remap period to the amount of readings:
int RemapedAmountOfReadings = map(PeriodBetweenPulses, 40000, 5000, 1, 10);  // Remap the period range to the reading range.
// 1st value is what are we going to remap. In this case is the PeriodBetweenPulses.
// 2nd value is the period value when we are going to have only 1 reading. The higher it is, the lower RPM has to be to reach 1 reading.
// 3rd value is the period value when we are going to have 10 readings. The higher it is, the lower RPM has to be to reach 10 readings.
// 4th and 5th values are the amount of readings range.
RemapedAmountOfReadings = constrain(RemapedAmountOfReadings, 1, 10);  // Constrain the value so it doesn't go below or above the limits.
AmountOfReadings = RemapedAmountOfReadings;  // Set amount of readings as the remaped value.

}
else
{
PulseCounter++; // Increase the counter for amount of readings by 1.
PeriodSum = PeriodSum + PeriodBetweenPulses; // Add the periods so later we can average.
}

} // End of Pulse_Event.

this is the code for RPM Counter.

ive been working on arduino ignition system for my bike . in simple terms i have incoming signal pulse coming from engine which is responsible to trigger ignition coil. when the engine is running at lower rpm then there should be a max delay of 5mS between signal pulse and igniton coil triggering. for example if engine send a signal pulse then the ignition coil trigger after 5ms. and this delay should be decrease accordingly to the engine RPM increases ( at 13000 RPM delay must be 0). im beginner to arduino and this advance retard technology is too old but nobody has solid answer on the internet not even the arduino forum. all they do is making conversation complicating and then no result. if anyone can help me with the code that would be greatly appericiated

What signal do you have to tell you RPM?

there is PULSAR COIL in the engine which is responsible for producing a signal pulse and this would also be responsible for creating RPM measurements.

To start with, I’d hook up whatever electronics you need to read the coil signal into your Arduino and use an interrupt to figure out RPM and display it.

@kassiano742 : I think you need to be more specific about what your problem is. You can’t realistically ask people on this forum to write your code for you.

The basic approach would be to measure the period between successive trigger signals, and calculate the RPM. Use the calculated RPM as an index into an array, in which you have your ignition timing map. Read out from the array the required delay, wait for the delay period and then trigger the ignition.

The RPM will be a signal in the range from 0 to 13,000, so I would probably divide that by 1000, to give an integer value between 0 and 13, which is an easy number for your map array. This would mean the ignition timing jumps to a new value each time it revs through a thousand rpm threshold. It may be that this is too coarse, but don’t worry - get it working first, then refine the map if you need to.

In the map array, you need the delay value in microseconds. Thus it would go from 0 at the high-speed end of the array, to 5000 at the low speed end.

You need to pay some attention to the electronics that handle the signal from the ignition pickup. Make sure it presents a clean, sharp-edged signal to the Arduino at all RPMs, including at cranking or kick-starting speeds.

Also, make sure you protect the Arduino from spikes on the output port driving the ignition system.

i dont know how to use interrupt and i dont know what is this. all i can understand is to put the signal at the analog pin on the arduino mega board which i have but im not sure how to do code for that to calculate rpm

So, having written all that (above), I want to make you aware of a problem. My microcontroller efforts with engines have proved very difficult: Arduinos will crash left, right and centre when anywhere near the high-voltage spikes from an ignition system.

You will probably need to protect it against both electric and magnetic fields, for which the best material is tin-plated steel (as used in food cans, biscuit tins, etc. To avoid crashes, I fully enclose my Arduino and the surrounding electronics in a box made from this material. Leads out to sensors or actuators are shielded, and it helps to have diode protection at the points where the leads enter the enclosure.

I’d guess that searching for RPM and Arduino will get you some code. But as SteveThackery points out, it’s defending the Arduino from the noisy electrics that is most important.

thanks you explained it very well. ive done signal condition to convert the raw spike of incoming signal to a square signal for arduino analog input. and also made a IGBT ignition coil driver electrically isolated from arduino. now my challenge is to make a RPM calculator first. i have tried but lot of examples are with the delay function which would work at all and some of them i found without delay but that are not precise not even close . i dont know what to do now

1 Like

@kassiano742: With great respect, I honestly think you need to put in a bit more work yourself. It is far better to try something, show it to us and ask why it doesn’t work. At the moment you don’t seem to have done that.

But the basic approach is this: you monitor the trigger input, and when it appears, read and store a “timestamp” using the micros() function. Wait for the next trigger. When that appears, read micros() again and calculate the difference between the new value and the older one you stored earlier, which will give you the period between the two pulses in microseconds. Convert that to RPM. Repeat as required.

To start with, just print out the RPM on the serial port back to the computer, so you can make sure it’s producing a sensible answer.

As you are a beginner, I suggest you get the really basic functionality working first: calculate the RPM from two pulses and print it via the serial port. After that you might want to measure several pulses and calculate a rolling average for the RPM (which will reduce the effects of noise or jitter on the trigger signal).

Once you’ve got that working, then you can think about looking up a delay value from your ignition timing map array, and displaying that.

One way of producing a map array is with Excel, because you can plot a graph of it and see if your curve is a suitable shape (probably just a straight line in your case). With a bit of messing about with cuts and pastes, you can create a text string which you can paste straight into the array definition in your Arduino code. I’ve recently developed an Arduino-based fuel injection system for a model engine, and created the map array like this.

Do it tiny step by tiny step until the whole thing is working.

You wouldn’t normally develop a software project quite like this, but for a beginner it’s highly recommended.

ive got something on the youtube . seems like the guy put a great effort on it. sir you should take a look on it . please :slight_smile:

const byte PulsesPerRevolution = 2; // Set how many pulses there are on each revolution. Default: 2.

// If the period between pulses is too high, or even if the pulses stopped, then we would get stuck showing the
// last value instead of a 0. Because of this we are going to set a limit for the maximum period allowed.
// If the period is above this value, the RPM will show as 0.
// The higher the set value, the longer lag/delay will have to sense that pulses stopped, but it will allow readings
// at very low RPM.
// Setting a low value is going to allow the detection of stop situations faster, but it will prevent having low RPM readings.
// The unit is in microseconds.
const unsigned long ZeroTimeout = 100000; // For high response time, a good value would be 100000.
// For reading very low RPM, a good value would be 300000.

// Calibration for smoothing RPM:
const byte numReadings = 2; // Number of samples for smoothing. The higher, the more smoothing, but it’s going to
// react slower to changes. 1 = no smoothing. Default: 2.

/////////////
// Variables:
/////////////

volatile unsigned long LastTimeWeMeasured; // Stores the last time we measured a pulse so we can calculate the period.
volatile unsigned long PeriodBetweenPulses = ZeroTimeout+1000; // Stores the period between pulses in microseconds.
// It has a big number so it doesn’t start with 0 which would be interpreted as a high frequency.
volatile unsigned long PeriodAverage = ZeroTimeout+1000; // Stores the period between pulses in microseconds in total, if we are taking multiple pulses.
// It has a big number so it doesn’t start with 0 which would be interpreted as a high frequency.
unsigned long FrequencyRaw; // Calculated frequency, based on the period. This has a lot of extra decimals without the decimal point.
unsigned long FrequencyReal; // Frequency without decimals.
unsigned long RPM; // Raw RPM without any processing.
unsigned int PulseCounter = 1; // Counts the amount of pulse readings we took so we can average multiple pulses before calculating the period.

unsigned long PeriodSum; // Stores the summation of all the periods to do the average.

unsigned long LastTimeCycleMeasure = LastTimeWeMeasured; // Stores the last time we measure a pulse in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.
unsigned long CurrentMicros = micros(); // Stores the micros in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.

// We get the RPM by measuring the time between 2 or more pulses so the following will set how many pulses to
// take before calculating the RPM. 1 would be the minimum giving a result every pulse, which would feel very responsive
// even at very low speeds but also is going to be less accurate at higher speeds.
// With a value around 10 you will get a very accurate result at high speeds, but readings at lower speeds are going to be
// farther from eachother making it less “real time” at those speeds.
// There’s a function that will set the value depending on the speed so this is done automatically.
unsigned int AmountOfReadings = 1;

unsigned int ZeroDebouncingExtra; // Stores the extra value added to the ZeroTimeout to debounce it.
// The ZeroTimeout needs debouncing so when the value is close to the threshold it
// doesn’t jump from 0 to the value. This extra value changes the threshold a little
// when we show a 0.

// Variables for smoothing tachometer:
unsigned long readings[numReadings]; // The input.
unsigned long readIndex; // The index of the current reading.
unsigned long total; // The running total.
unsigned long average; // The RPM value after applying the smoothing.

void setup() // Start of setup:
{

Serial.begin(9600); // Begin serial communication.
attachInterrupt(digitalPinToInterrupt(2), Pulse_Event, RISING); // Enable interruption pin 2 when going from LOW to HIGH.

delay(1000); // We sometimes take several readings of the period to average. Since we don’t have any readings
// stored we need a high enough value in micros() so if divided is not going to give negative values.
// The delay allows the micros() to be high enough for the first few cycles.

} // End of setup.

void loop() // Start of loop:
{

// The following is going to store the two values that might change in the middle of the cycle.
// We are going to do math and functions with those values and they can create glitches if they change in the
// middle of the cycle.
LastTimeCycleMeasure = LastTimeWeMeasured; // Store the LastTimeWeMeasured in a variable.
CurrentMicros = micros(); // Store the micros() in a variable.

// CurrentMicros should always be higher than LastTimeWeMeasured, but in rare occasions that’s not true.
// I’m not sure why this happens, but my solution is to compare both and if CurrentMicros is lower than
// LastTimeCycleMeasure I set it as the CurrentMicros.
// The need of fixing this is that we later use this information to see if pulses stopped.
if(CurrentMicros < LastTimeCycleMeasure)
{
LastTimeCycleMeasure = CurrentMicros;
}

// Calculate the frequency:
FrequencyRaw = 10000000000 / PeriodAverage; // Calculate the frequency using the period between pulses.

// Detect if pulses stopped or frequency is too low, so we can show 0 Frequency:
if(PeriodBetweenPulses > ZeroTimeout - ZeroDebouncingExtra || CurrentMicros - LastTimeCycleMeasure > ZeroTimeout - ZeroDebouncingExtra)
{ // If the pulses are too far apart that we reached the timeout for zero:
FrequencyRaw = 0; // Set frequency as 0.
ZeroDebouncingExtra = 2000; // Change the threshold a little so it doesn’t bounce.
}
else
{
ZeroDebouncingExtra = 0; // Reset the threshold to the normal value so it doesn’t bounce.
}

FrequencyReal = FrequencyRaw / 10000; // Get frequency without decimals.
// This is not used to calculate RPM but we remove the decimals just in case
// you want to print it.

// Calculate the RPM:
RPM = FrequencyRaw / PulsesPerRevolution * 60; // Frequency divided by amount of pulses per revolution multiply by
// 60 seconds to get minutes.
RPM = RPM / 10000; // Remove the decimals.

// Smoothing RPM:
total = total - readings[readIndex]; // Advance to the next position in the array.
readings[readIndex] = RPM; // Takes the value that we are going to smooth.
total = total + readings[readIndex]; // Add the reading to the total.
readIndex = readIndex + 1; // Advance to the next position in the array.

if (readIndex >= numReadings) // If we’re at the end of the array:
{
readIndex = 0; // Reset array index.
}

// Calculate the average:
average = total / numReadings; // The average value it’s the smoothed result.

// Print information on the serial monitor:
// Comment this section if you have a display and you don’t need to monitor the values on the serial monitor.
// This is because disabling this section would make the loop run faster.
Serial.print("Period: “);
Serial.print(PeriodBetweenPulses);
Serial.print(”\tReadings: “);
Serial.print(AmountOfReadings);
Serial.print(”\tFrequency: “);
Serial.print(FrequencyReal);
Serial.print(”\tRPM: “);
Serial.print(RPM);
Serial.print(”\tTachometer: ");
Serial.println(average);

} // End of loop.

void Pulse_Event() // The interrupt runs this to calculate the period between pulses:
{

PeriodBetweenPulses = micros() - LastTimeWeMeasured; // Current “micros” minus the old “micros” when the last pulse happens.
// This will result with the period (microseconds) between both pulses.
// The way is made, the overflow of the “micros” is not going to cause any issue.

LastTimeWeMeasured = micros(); // Stores the current micros so the next time we have a pulse we would have something to compare with.

if(PulseCounter >= AmountOfReadings) // If counter for amount of readings reach the set limit:
{
PeriodAverage = PeriodSum / AmountOfReadings; // Calculate the final period dividing the sum of all readings by the
// amount of readings to get the average.
PulseCounter = 1; // Reset the counter to start over. The reset value is 1 because its the minimum setting allowed (1 reading).
PeriodSum = PeriodBetweenPulses; // Reset PeriodSum to start a new averaging operation.

// Change the amount of readings depending on the period between pulses.
// To be very responsive, ideally we should read every pulse. The problem is that at higher speeds the period gets
// too low decreasing the accuracy. To get more accurate readings at higher speeds we should get multiple pulses and
// average the period, but if we do that at lower speeds then we would have readings too far apart (laggy or sluggish).
// To have both advantages at different speeds, we will change the amount of readings depending on the period between pulses.
// Remap period to the amount of readings:
int RemapedAmountOfReadings = map(PeriodBetweenPulses, 40000, 5000, 1, 10);  // Remap the period range to the reading range.
// 1st value is what are we going to remap. In this case is the PeriodBetweenPulses.
// 2nd value is the period value when we are going to have only 1 reading. The higher it is, the lower RPM has to be to reach 1 reading.
// 3rd value is the period value when we are going to have 10 readings. The higher it is, the lower RPM has to be to reach 10 readings.
// 4th and 5th values are the amount of readings range.
RemapedAmountOfReadings = constrain(RemapedAmountOfReadings, 1, 10);  // Constrain the value so it doesn't go below or above the limits.
AmountOfReadings = RemapedAmountOfReadings;  // Set amount of readings as the remaped value.

}
else
{
PulseCounter++; // Increase the counter for amount of readings by 1.
PeriodSum = PeriodSum + PeriodBetweenPulses; // Add the periods so later we can average.
}

} // End of Pulse_Event.

You don’t post code like that - it needs to be properly formatted. Check the forum for the instructions.

But anyway, why post all that code? We want to see your code, and then comment on it. But by all means read carefully through what you have found and learn from it.

If you are still struggling, having made an attempt, we can help.

But in any case, the basic approach is very simple: use the micros() function to create timestamps, read the interval between them, convert to RPM, display it.

Errrmmm… I’m not sure what role the delay() function would have when calculating RPM. All I can do is refer you to my previous posts, especially the summary I gave of the process: use the micros() function to create timestamps, read the interval between them, convert to RPM, display it.

I wouldn’t even do it with an interrupt to start with. Just sit scanning the input port for a pulse. When it arrives, timestamp it and compare it with the last one, then send it back to your PC using Serial.print().

I suggest not to use an interrupt simply because scanning the port is easier to understand to begin with, and definitely much easier to debug. Once you’ve got some kind of believable output, then convert it to use an interrupt. Like I say, baby steps is the way to go.

Just write some code which does that, and get back to us.

@kassiano742

I’ve found some code I used for counting RPM on my motorcycle. The great majority of the code is a state machine designed to separate real ignition pulses from noise - a common problem with vehicle electronics. Also the code reports the RPM to an I2C master, which isn’t what you want. However, you will see how the RPM is calculated.

This is the complete code:

/*
  This is an I2C slave.
  Monitors the ignition pulses, calculates the RPM, and reports
  this back to the I2C Master.

  9/3/2015, Steve Thackery
*/


#define ignPin 3
#define ledPin 13
#define maxRPM 6000


#include <Wire.h>
#include <stdlib.h>
#include "E:\Documents\Arduino\Projects\Bullet_data_logger_V4\Bullet_data_logger.h"


//constants for state machines
const byte rpmWaitingStart = 0;
const byte rpmWaitingStartConf = 1;
const byte rpmWaitingEnd = 2;
const byte rpmWaitingEndConf = 3;

const byte ledOff = 0;
const byte ledOn = 1;

const int noiseDelay = 500;    //microseconds to confirm a pulse is not noise
const int ignSettlingDelay = 5000;   //microseconds for ignition circuit to calm down
const int ledOnPeriod = 50;    //milliseconds
const int ledOffPeriod = 450;


//globals
String rpm = "00000";
byte rpmState = 0;  //to store the states of the state machines
byte ledState = 0;

//functions

void sendData()
{
  char dataArray[10] = "";    //leave room for longer strings
  dataArray[0] = char(rpm.length());
  rpm.toCharArray(&dataArray[1], int(dataArray[0] + 1));

  Wire.write(dataArray);
}


void updateRPM()
{
  //ignition pulse is active low

  static unsigned long int oldTime = 0;
  static unsigned long int newTime;
  static unsigned long int confTime;
  static unsigned long int intRPM;

  switch (rpmState)
  {
    case rpmWaitingStart:
      {
        if (digitalRead(ignPin) == LOW)
        {
          newTime = micros();    //record the timestamp
          confTime = newTime + noiseDelay;
          rpmState = rpmWaitingStartConf;
        }
      }
      break;

    case rpmWaitingStartConf:
      {
        if (micros() >= confTime)    //time to see if pulse is still active
        {
          if (digitalRead(ignPin) == LOW)         //pulse is still low after confTime - thus valid
          {
            digitalWrite(ledPin, HIGH);
            intRPM = newTime - oldTime;  //period, not RPM yet
            intRPM = (120000000 / intRPM);  //now in rpm

            if (intRPM <= maxRPM)        //sanity check
            {
              rpm = String(intRPM);        //convert to string and store
            }
            else
            {
              rpm = String(maxRPM);
            }

            oldTime = newTime;        //ready for the next ignition pulse
            rpmState = rpmWaitingEnd;
          }
          else    //ignPin not still low, so it was noise
          {
            rpmState = rpmWaitingStart;  //start over
          }
        }  //not got to confTime yet, so do nothing
      }
      break;

    case rpmWaitingEnd:
      {
        if (digitalRead(ignPin) == HIGH)
        {
          confTime = micros() + ignSettlingDelay;
          rpmState = rpmWaitingEndConf;
        }
      }
      break;

    case rpmWaitingEndConf:
      {
        if (micros() > confTime)  //time to see if pulse is still inactive
        {
          if (digitalRead(ignPin) == HIGH)
          {
            digitalWrite(ledPin, LOW);
            rpmState = rpmWaitingStart;  //ready for next ignition pulse
          }
          else    //ignPin not still high, so it was noise
          {
            rpmState = rpmWaitingEnd;
          }
        }  //not got to conftTime yet, so do nothing
      }
      break;
  }
}


void setup()
{
  pinMode(ignPin, INPUT);
  pinMode(ledPin, OUTPUT);

  Wire.onRequest(sendData);

  Wire.begin(rpmAddress);
}


void loop()
{
  updateRPM();
}

The part relevant to you, which timestamps the ignition pulse is this:

        if (digitalRead(ignPin) == LOW)
        {
          newTime = micros();    //record the timestamp

The part which calculates the RPM is this:

            intRPM = newTime - oldTime;  //period, not RPM yet
            intRPM = (120000000 / intRPM);  //now in rpm

            //snip

            oldTime = newTime;        //ready for the next ignition pulse

There is a bug in this code, to do with when micros() overflows. It’s an easy fix which you can find with the help of Google.

Also, note that there is no “smoothing” or averaging of the RPM with this code. If I were to write it now, I would pass the RPM through a simple low-pass filter before converting to a string ready to send.

Here is a low-pass filter example:

  #define SMOOTHING_FACTOR 0.8  //between 0 (no smoothing) and 0.99 (maximum smoothing)
  
  static float smoothedValue = 0;
  float unsmoothedValue = 0;

  unsmoothedValue = GetTheCurrentDataValue();

  smoothedValue = (SMOOTHING_FACTOR * smoothedValue) + ((1 - SMOOTHING_FACTOR) * unsmoothedValue);  //low pass filter

Let us know how you get on… :grinning: