Getting RPM from a missing tooth wheel with a Hall Sensor

So I am currently trying to get the RPM from a crankshaft sensor in a vehicle. It uses a 5v hall sensor which produces a square wave output, the hall sensor is pointed at a metal wheel with 36 evenly spaced teeth, but one of the teeth is ground down/cut off/missing. (known as a missing tooth wheel, 36-1) To get a pretty signal for bench testing, I'm using Ardustim on another Arduino to generate the signal.

When the wheel spins, The missing tooth section is used by the original ECU to calculate what degree of rotation the engine is currently in, in order to accurately trigger ignition/fuel injection events. I don't actually care about doing any of this, I just want a way of converting the pulses to a revolutions per minute so I can log the data to pc/sd card etc.

My current method was taken from some code posted years ago on this forum, we define a 'Multiplier' number which uses the following calculation: Microseconds in a minute / the number of pulses in one full revolution. So 60,000,000 / 36 = 1666666.66666666667

It then uses a rising tooth trigger ISR to calculate the time between the start of one tooth to the next, and then divide the multiplier by delta Time = RPM.

This works well until the missing tooth comes around, the delta T is doubled and RPM is halved (double duration between teeth). Ideally i would like to be able to write the code to deal with the missing tooth properly, maybe by counting 35 teeth and then skipping a count? Easy way out is just by deleting the rows of data afterwards, but that's a cheat. I thought about maybe doing an if statement in the ISR, and checking if the deltaT = 2 * the last deltaT, and then resetting the tooth counter to 0 for counting to 35 teeth? But I wasn't too sure if that would make the ISR too long... I can do the basics for Arduino but never really played with this stuff too much haha!

Any help is much appreciated!! Even if it's just a pointer to somewhere that may help me out haha!

Sample serial output:


ToothDeltaT,1256.00
RPM,1326.96

ToothDeltaT,1256.00
RPM,1335.47

ToothDeltaT,1256.00
RPM,1335.47

ToothDeltaT,2512.00
RPM,663.48

ToothDeltaT,1248.00
RPM,1326.96

ToothDeltaT,1252.00
RPM,1322.75

ToothDeltaT,1256.00
RPM,1326.96


#define ClockPin 2 // Must be pin 2 or 3

      // Multiplier = (60 seconds * 1000000 microseconds) microseconds in a minute / the number of potential pulses in 1 full revolution)
      // My engine has a 36-1 toothed wheel, so 60,000,000 / 36 = 1,666,666.666666667
      
#define Multiplier 1666666.66666666667 // don't forget a decimal place to make this number a floating point number
volatile long count = 0;
volatile long EncoderCounter = 0;
volatile float SpeedInRPM = 0;
volatile float ToothGap = 0;

void onPin2CHANGECallBackFunction(){ 
    static uint32_t lTime; // Saved Last Time of Last Pulse
    uint32_t cTime; // Current Time
    cTime = micros(); // Store the time for RPM Calculations
    int32_t dTime; // Delt in time

    // calculate the DeltaT between pulses
    dTime = cTime - lTime; 
    lTime = cTime;
    SpeedInRPM = Multiplier / dTime; // Calculate the RPM Switch DeltaT to either positive or negative to represent Forward or reverse RPM
    ToothGap = dTime;
}


void setup() {
  Serial.begin(115200); //115200
  // put your setup code here, to run once:
  pinMode(ClockPin, INPUT);  
  attachInterrupt(digitalPinToInterrupt(ClockPin),onPin2CHANGECallBackFunction,RISING);
}

void loop() {
  //  long Counter;
  float Speed;
  float toothDeltaT;
  
  noInterrupts (); 
  // Because when the interrupt occurs the EncoderCounter and SpeedInRPM could be interrupted while they 
  // are being used we need to say hold for a split second while we copy these values down. This doesn't keep the 
  // interrupt from occurring it just slightly delays it while we maneuver values.
  // if we don't do this we could be interrupted in the middle of copying a value and the result get a corrupted value.
  Speed = SpeedInRPM;
  toothDeltaT = ToothGap;
  interrupts ();

  Serial.print("RPM,");
  Serial.println(Speed);
  Serial.print("ToothDeltaT,");
  Serial.println(ToothGap);
 
}

I would have the isr simply count the number of teeth that it sees.
Then make a function to calculate RPM.

int rpm() {
   static uint32_t lastCount;
   static uintt32_t lastMillis;
   int rpm=(count-lastCount)*1000*60/(millis()-lastMillis)/35;
   lastCount = count;
   lastMillis = milllis();
   return rpm;
}

You should not call this function more often than once per second...
And you need to protect lastCount from the isr.

Here is a crank angle sensor sketch I wrote a while ago. It might provide a useful base.

// Example CrankAngleSensor.ino
// Written by John Wasser
// 16-Jul-2018

const byte CrankPulsePin = 2;  // Must be External Interrupt pin
const byte PulsesPerRevolution = 12; // INCLUDE THE 'MISSING' PULSES


volatile unsigned long PulseInterval;  // Microseconds between the last two pulses
volatile unsigned long PulseTime;  // Time (micros()) of the rising edge of the latest pulse
volatile byte PulseNumber = 0;  // Which pulse was the latest pulse
volatile boolean PulseIsNew = false;   // Flag set by the ISR

void myPulseIsr()
{
  static unsigned long prevPulseTime;  // Needed for detecting the new pulse interval
  static unsigned long prevPulseInterval;  // Needed for detecting the long pulse

  PulseTime = micros();
  prevPulseInterval = PulseInterval;  // save the preceding value
  PulseInterval = PulseTime - prevPulseTime;
  prevPulseTime = PulseTime;
  PulseNumber++;
  // A pulse longer than 1.5 times the previous pulse indicates the index pulse (#0).
  if (PulseInterval > (prevPulseInterval + (prevPulseInterval >> 1)))   // compare to prev * 1.5
  {
    PulseNumber = 0;
  }
  PulseIsNew = true;  // Let loop() know that new data is available
}

void setup()
{
  attachInterrupt(digitalPinToInterrupt(CrankPulsePin), myPulseIsr, RISING);
}

void loop()
{
  unsigned long localPulseInterval;
  static unsigned long localPulseTime = 0;
  static byte localPulseNumber = 0;
  static unsigned long microsecondsPerRevolution = 999;

  if (PulseIsNew)
  {
    // Grab the volatile variables.
    noInterrupts();
    localPulseInterval = PulseInterval; // Should work down to 152 RPM
    localPulseTime = PulseTime;
    localPulseNumber = PulseNumber;
    PulseIsNew = false;
    interrupts();

    microsecondsPerRevolution = localPulseInterval * PulsesPerRevolution;
    if (localPulseNumber == 0)
    {
      microsecondsPerRevolution = localPulseInterval * (PulsesPerRevolution / 2); // Long Pulse
    }
  }

  unsigned long microsecondsSincePulse = micros() - localPulseTime;
  long int degreesSinceIndex = (360 * localPulseNumber)  / PulsesPerRevolution ;
  degreesSinceIndex += (microsecondsPerRevolution * 360UL) / microsecondsSincePulse;

  //   THIS IS WHERE YOU ADJUST 'degreesSinceIndex' for offset from TDC

  // This is where you use the new crank position
}
1 Like

Absolutely brilliant haha! This worked beautifully thanks John.

Been going crazy thinking of a proper way of doing it, instead of just deleting out of excel afterwards haha...

void onPin2CHANGECallBackFunction(){ 
    static uint32_t lTime; // The time of the last pulse in micros
    static uint32_t lastDTime; // the last deltaT value
    uint32_t cTime; // The current time in micros
    cTime = micros(); // Store the current time for RPM Calculations
    int32_t dTime; // The current deltaT 
    
    dTime = cTime - lTime; // calculate the DeltaT between the lTime pulse and the cTime pulse
    lTime = cTime; // Set the lTime variable for the next trigger

    // if dTime is less than the last dTime + (dTime / 2), then update RPM. 
    if (dTime < (lastDTime + (lastDTime >> 1))){
    SpeedInRPM = Multiplier / dTime;
    }
    
    lastDTime = dTime; // Set lastDTime variable for the next trigger.
}


All that's required is to count interrupts, and calculate RPM every 35th interrupt.

void crankSensorIRQ(void)
{
    static unsigned long startTime = 0;
    static int irqCnt = 0;

    if (++irqCnt == 35)
   {
        unsigned long now = millis();
         // Calculate RPM here, using (now - startTime)
        startTime = now;  // Setup for next reading
        irqCnt = 0;
   }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.