RPM detection and pulses per revolution

Hello everybody.
I am working on module that takes rpm signal of car (old, no obd or anything fancy) and make it more 'readable' by microprocessor. It have diesel engine so signal is made by so called 'pickup sensor' inside injection pump. Sensor is pointet and toothed wheel with 23 teeths, thus generating 23 waves of ac signal per 2 engine revolutions (engine and injection pump rotate in 2:1 ratio) or 11,5 waves per one engine revolution. I designed a circuit that amplify signal, make is square using Schmitt trigger and at the end is counter with some logic gates that translate those 11,5 pulses to 1 pulse per revolution.
Question is, do number of pulses per revolution matter? It is whole rpm value more accurate? Or I can make my arduino's life more easier by lowering number of pulses to work with?

Thanks for your input :slight_smile:

Unless you are concerned with fractional RPM, there is no good reason for not making it simply one pulse per revolution.

Paul

For simplicity I would not lower the number of pulses per rev. Just feed the Schmitttrigger signal directly to the Arduino. Car diesels typically work @2000 RPM = 33.3 Hz. Maybe a maximum @6000 RPM x11.5 =1150 Hz, which is still not a problem for Arduino.
The accuracy depends on how you write the code. If you measure the time it takes to produce a number of pulses the result will be nicely accurate. Use the micros() function. If you measure the amount of pulses in a fixed time, the result will not be so accurate especially for the one pulse per rev.

If you measure the time it takes for a given number of pulses, it won't report when the engine stops.

However if you count the number of pulses in a given time interval, you'll get a value that always updates and is proportional to the speed. You can even use the T1 input on some ATmega arduinos to count the pulses in timer
hardware directly. (On the ATmega328 timer one counter input pin is Arduino pin 5, for instance).

MarkT:
If you measure the time it takes for a given number of pulses, it won't report when the engine stops.

That could be solved by some 'time-out' function that set maximum time for measurement, otherwise it would report 0 rpm.

So number of pulses per revolution do not matter that much long as it within microprocessor limits but it could hurt accuracy in some conditions.

And since you both mentied different measuring methods, 1) no. pulses in fixed time frame vs 2) time to achive specified no. pulses, which one of those is better? :smiley: I know you'll probably ask 'it depends on what you want to do with that information and how accurate it needs to be'. Plan is to have a module that take rpm singal do all conditining and counting, then send it to CAN bus network (yes, we are creating custom CAN Bus network in said vehicle :wink: ). Data then will be used by instrument cluster (to show rpm value), by A/C control module (a/c shuts off when rpm falls under idle), and by Idle Throttle Control (DIY module, increase throttle to keep set rpm, aka idle-up for cold engine, idle-up when a/c is on and to prevent engine stall due to low rpm). So RPM value needs to be somehow accurate but not super accurate.

Thanks,
M :slight_smile:

The micros() function gives you micro second resolution which is more than enough to measure the time between pulses @6000 rpm. But it depends on the rest what you want to do in the loop, whether this may leads to inaccurate results or not. If the canbus takes up more than a ms, you might miss counts. If you measure the time between consecutive pulses, a missed count is 100% error.

I would probably measure for a fixed time. Count the number of pulses, store the startTime and the lastPulseTime. The time between the pulses = (lastPulseTime-startime) / count.

A little test see code below

Pin 13 generates pulses and is connected to pin 8
The frequency can be changed by changing the divider in (micros()/100)

The fuction pulseTime is true when a new datapoint is ready.
The code runs fine up to 15 kHz. The lower limit is determined by the number of microseconds measuring time 100000µs = 0.1s=10Hz

The accuracy is excellent over the whole range, better than 0.1%
The trick of setting count = -1 gives each measurement period it's own starting point. Somehow this was necessary to get this accuracy. Setting it to 0 instead, uses the endpoint of the last measuring period, but this was far less accurate.

So this also answers your first question, whether you should slow down the pulses electronically. Don't do that. The idle throttle control will work better if it receives the RPM data quicker.

const byte inPin=8,
           outPin=13;
float RPM=0; 

void setup() 
{  pinMode(inPin,INPUT);
   pinMode(outPin,OUTPUT);
   Serial.begin(115200);
}
void loop() 
{ if (pulseTime(100000)) Serial.println(RPM);  // measuring period in µs
  digitalWrite(outPin,(micros()/100) %2);  //  generate pulses freq=1MHz/divider  
}

boolean pulseTime (unsigned long duration)
{  static int lastInput= digitalRead(inPin);
   static unsigned long start= micros(), lastEventTime = start ; 
   static long count = -1 ; 
   if ((micros()- start) < duration)           // measure
   {   if (lastInput != digitalRead(inPin) )
       {   lastInput = digitalRead(inPin);
      //     if (lastInput)       // uncomment to count half pulses
           {  lastEventTime = micros();
              count++;
              if (count==0) start=micros();
           } 
       }
       return false;
   }
   else
   {  if (count==0 ||  lastEventTime==start ) RPM=0; 
      else RPM = (6.0E7*float(count)/float(lastEventTime-start) );
   //   Serial.print(count); Serial.print("  ");
      count=-1;
      start=micros();
      return true;
   }   
}

I reckon the logic can be neater/simpler - first split into separate functions, secondly don't keep reseting variables, just take differences:

int pulses = 0, lastPulses = 0 ;
long eventTime = 0L, lastEventTime = 0L ;


void report_rpm ()  // using differences makes code simpler
{
  Serial.println ((pulses - lastPulses) * 6e7 / (eventTime - lastEventTime)) ;
  lastPulses = pulses ;
  lastEventTime = eventTime ;
}


void account_pulses ()
{
  pulses ++ ;
  eventTime = micros() ;
}



long prev_time = 0L ;
#define PERIOD_US 100000L

void loop()
{
  if (micros() - prev_time >= PERIOD_US)  // regularly report
  {
    report_rpm () ;
    prev_time += PERIOD_US ;
  }
  
  bool thisInput = digitalRead (inPin) ;
  if (lastInput != thisInput)   // account for every transition
  {
    account_pulses () ;
    lastInput = thisInput ;
  }
}

Simpler more direct code has fewer places for bugs to creep in. Separating into smaller functions always improves readability.

Oh well thanks for ideas and suggestions. I will try those soon as I am able to do so :slight_smile: If those works then I can add mcp2515 (can bus module) and see how it works together.
Also I noticed that none of your codes uses interrupts (or I am wrong?) but many rpm codes I did study and try, use it. Can you explain why? My own explanation is that my planned application does not require it since processor do only pulse counting and later on Can bus messaging (only sending, no receiving).

Thanks, M :slight_smile:

Interrupts would definitely be appropriate, but its more complex and when showing proof-of-concept code
its best keep it simple and obvious.