Hardware timer and interrupts issue

Hello everyone,

I recently started programming with Arduino and and currently I'm making a project to read RPM and Speed of my car using Arduino Uno. So far I got everything working but the speed value does not show properly. I have used one hardware counter and two interrupts in this project.

  1. Timer 1 on pin 5 - Count pulses from car's ECU to calculate speed.
  2. Int0 on pin 2 - Read ignition pulse to calculate RPM.
  3. int1 on pin 3 - 5v in from headlight switch to control the brightness of the display (MAX729 7 seg 8 digit).

I can get the proper reading from RPM and also the brightness control is working properly. Here is my codes below.

#include <LedControl.h>

// inputs: DIN pin, CLK pin, LOAD pin. number of chips
LedControl display = LedControl(12, 10, 11, 1);

//SPEEDO-----------------------------------------------------------
const int hardwareCounterPin = 5;
const int samplePeriod = 1000; //in milliseconds
const float pulsesPerMile = 4000; // this is pulses per mile for Toyota. Other cars are different.
const float convertMph = pulsesPerMile/3600;
unsigned int count;
float mph;
int kph;
int roundedKph;
int previousKph;
int prevCount;
//----------------------------------------------------------------

//RPM-------------------------------------------------------------
//Configuration for the Tachometer variables
const int sensorPin = 2;
const int sensorInterrupt = 0;
const int timeoutValue = 5;
volatile unsigned long lastPulseTime;
volatile unsigned long interval = 0;
volatile int timeoutCounter;
int rpm;
int rpmlast = 3000;
int rpm_interval = 3000;
//---------------------------------------------------------------

//Brightness control---------------------------------------------
int brightnessIndicator = 13;  //Light pin 13 led to show that brightness control is in action.
int brightnessIn = 3;  //Sensor in from headlight positive wire to control the brightness.
//---------------------------------------------------------------

void setup(void) {
  //Serial.begin(9600);
  
  display.shutdown(0, false);  // turns on display
  display.setIntensity(0, 10); // 15 = brightest
  printTrueno(); //Print my car's name
  delay(3000);
  initToZero(); //Print all zeros before going to the loop
  
  TCCR1A = 0; //Configure hardware counter 
  TCNT1 = 0;  // Reset hardware counter to zero
  
  //Config for the Tach
  pinMode(sensorPin, INPUT);
  attachInterrupt(sensorInterrupt, &sensorIsr, RISING);
  lastPulseTime = 0;
  timeoutCounter = 0;
  
  //Config for brightness control
  pinMode(brightnessIndicator, OUTPUT);  //LED output
  pinMode(brightnessIn,INPUT);  //Sensor in
  attachInterrupt(1, changeBrightness, CHANGE);  //Attach the 2nd interrupt
  changeBrightness();  //Check for head lights on or not.
}
 
void loop() {
  //SPEEDO-----------------------------------------------------------
  // This uses the hardware pulse counter on the Arduino.
  // Currently it collects samples for one second.
  bitSet(TCCR1B, CS12); // start counting pulses
  bitSet(TCCR1B, CS11); // Clock on rising edge
  delay(samplePeriod); // Allow pulse counter to collect for samplePeriod
  TCCR1B = 0; // stop counting
  count = TCNT1; // Store the hardware counter in a variable
  TCNT1 = 0;     // Reset hardware counter to zero
  mph = (count/convertMph)*10; // Convert pulse count into mph. 10x allows retaining 10th of mph resolution.
  kph = ((unsigned int) mph)*1.6; // Convert to kph and cast to integer. 
 
  int x = kph / 10;
  int y = kph % 10;
  
  // Round to whole mile per hour
  if(y >= 5){
    roundedKph = x + 1;
  }
  else {
    roundedKph = x;
  }
  
  //If kph is less than 1 kph just show 0kph.
  //Readings of 0.9kph or lower are some what erratic and can
  //occasionally be triggered by electrical noise.
  //if(x == 0){
  if(x < 2){  // 1 was triggered by some noise signal.
    roundedKph = 0;
  }
  
  // Don't display kph readings that are more than 50 kph higher than the previous reading because it is probably a spurious reading.
  if(roundedKph < 181){
    if((roundedKph - previousKph) > 50) {
      Serial.println(previousKph);
      printNumber(previousKph, 0);
    }
    else {
      Serial.println(roundedKph);
      printNumber(roundedKph, 0);
    }
  }
  
  previousKph = roundedKph; // Set previousMph for use in next loop.
  //-----------------------------------------------------------------
  
  //RPM--------------------------------------------------------------
  if(rpm >= 0) {  //Remove the minus values   
    
    //Let's keep this RPMr value under control, between 0 and 9999
    rpm = constrain (rpm, 0, 9999);

    //If the engine is not running, print 0
    if ((micros() - lastPulseTime) < 5e6 ) {
      rpm = rpm;
    }
    else {
      rpm = 0;
    }
    
    if (rpm < 250)
      rpm = 0;
    //Serial.println(rpm);
    printNumber(rpm, 1);
  }
  //-----------------------------------------------------------------
}

//Each time the interrupt receives a rising tach signal, it'll run this subroutine
void sensorIsr() {
  unsigned long now = micros();
  interval = now - lastPulseTime;
  if (interval > 5000){
     rpm = 61000000UL/(interval * 2);
     lastPulseTime = now;
  }
}

void changeBrightness() {
  int val = digitalRead(brightnessIn);  //Read the sensor value
  
  if(val == 1){  //If the headlights are on, pin goes HIGH.
    digitalWrite(brightnessIndicator, HIGH);   //turn the LED on
    display.setIntensity(0, 6); //Reduce the brightness.
  }
  else {
    digitalWrite(brightnessIndicator, LOW);    //turn the LED off
    display.setIntensity(0, 14);  //increase the brightness.
  }
}

void printNumber(int v, int flag) {
    int ones;
    int tens;
    int hundreds;
    int thousands;
    
    if (flag==0){
      ones = v%10;
      v = v/10;
      tens = v%10;
      v= v/10;
      hundreds = v;

      //Now print the number digit by digit
      if (hundreds>0)
        display.setDigit(0,0,(byte)hundreds,false);
      else
        display.setChar(0,0,' ',false);
        
      display.setDigit(0,1,(byte)tens,false);       
      display.setDigit(0,2,(byte)ones,false);
    }
    else if (flag=1) {
      ones = v%10;
      v = v/10;
      tens = v%10;
      v = v/10;
      hundreds = v%10;
      v = v/10;
      thousands = v%10;

      //Now print the number digit by digit
      display.setDigit(0,4,(byte)thousands,false);
      display.setDigit(0,5,(byte)hundreds,false);
      display.setDigit(0,6,(byte)tens,false);
      display.setDigit(0,7,(byte)ones,false);
    }
}

I think the problem is between Timer1 and first interrupt. :fearful: Can someone please help me to solve this?

Thank you very much.

I can't immediately figure out what your code does but my impression is that it is unnecessarily complicated for a simple task.

Why not just have the ISR for the sensor count every pulse and then every second or so see how many new pulses there have been and work out the speed as pulses/second

There doesn't seem to be any need for an interrupt to detect the headlights ON situation - it does not change rapidly.

Something like this

void loop() {
   curMillis = millis();
   checkHeadLights();
   updateRPM();
   displayRPM();
}

void checkHeadlights() {
    headightStatus = digitalRead(headlightPin);
}

void updateRPM() {
    if (curMillis - prevMillis >= 1000) {
         prevMillis += 1000;
         currentRpmCount = rpmInterruptCount;
         rpmTicsPerSec = currentRpmCount - prevRpmCount; 
         prevRpmCount = currentRpmCount;
    }
}

void rpmISR() {
   rpmInterruptCount ++;
}

...R

  1. Timer 1 on pin 5 - Count pulses from car's ECU to calculate speed.
  2. Int0 on pin 2 - Read ignition pulse to calculate RPM.
  3. int1 on pin 3 - 5v in from headlight switch to control the brightness of the display (MAX729 7 seg 8 digit).

On which Arduino? Why does the Timer1 interrupt involve a pin?

const float pulsesPerMile = 4000; // this is pulses per mile for Toyota. Other cars are different.

Are you really expecting to get 4013.6 pulses per mile? Why is this variable a float?

Why do you need an interrupt to deal with the headlight switch? Is it really going to matter is the headlights come on, or go off, 100 nanoseconds late?

  kph = ((unsigned int) mph)*1.6; // Convert to kph and cast to integer.

There is no casting going on here. There is truncation.

  //SPEEDO-----------------------------------------------------------
  // This uses the hardware pulse counter on the Arduino.
  // Currently it collects samples for one second.
  bitSet(TCCR1B, CS12); // start counting pulses
  bitSet(TCCR1B, CS11); // Clock on rising edge
  delay(samplePeriod); // Allow pulse counter to collect for samplePeriod
  TCCR1B = 0; // stop counting
  count = TCNT1; // Store the hardware counter in a variable
  TCNT1 = 0;     // Reset hardware counter to zero

The use of interrupts AND delay() in the same code just screams cluelessness. I'm willing to bet that you could poll the ECU on every pass though loop,() as well as polling the headlight switch, and get acceptable results, if you were NOT using delay() anywhere.

I don't see where you implement the interrupt service routine that Timer1 is supposed to be triggering.

@vajira: Keep in mind that any call to delay() puts the board into a coma for the delay period you specify, so don't expect to "sense" anything happening during that period of time. If you really do need some kind of delay, check out the "Blink Without delay()" in the Examples section of the IDE.

Robin2:
I can't immediately figure out what your code does but my impression is that it is unnecessarily complicated for a simple task.

Why not just have the ISR for the sensor count every pulse and then every second or so see how many new pulses there have been and work out the speed as pulses/second

There doesn't seem to be any need for an interrupt to detect the headlights ON situation - it does not change rapidly.

Something like this

void loop() {

curMillis = millis();
  checkHeadLights();
  updateRPM();
  displayRPM();
}

void checkHeadlights() {
   headightStatus = digitalRead(headlightPin);
}

void updateRPM() {
   if (curMillis - prevMillis >= 1000) {
        prevMillis += 1000;
        currentRpmCount = rpmInterruptCount;
        rpmTicsPerSec = currentRpmCount - prevRpmCount;
        prevRpmCount = currentRpmCount;
   }
}

void rpmISR() {
  rpmInterruptCount ++;
}




...R

Thank you for the reply. I can remove the interrupt for the brightness control. But can I use 2 interrupts to read both RPM and speedo pulses in the same way as you mentioned above? The thing is when I run these two parts of codes separately (RPM and Speedo), they work without any problems and give me the correct values. But when I merge them together, the speedo value becomes incorrect. Lets say my current speed is 25kmph. But I'm seeing incorrect random values like 31, 35, 45, 40, etc... Sometimes after every 5-6 seconds I get the correct reading 25kmph. But my RPM values are showing correctly 99% of the time.

Thank you for the reply PaulS. I'm new to Arduino and this is like my first project. So there can be many mistakes.

PaulS:

On which Arduino? Why does the Timer1 interrupt involve a pin?

Im using Uno and use pin 5 to count pulse. This part of the code is from "Arduino Cookbook, 2nd Edition - Chapter 18.7"

Are you really expecting to get 4013.6 pulses per mile? Why is this variable a float?

That was a silly mistake.

The use of interrupts AND delay() in the same code just screams cluelessness. I'm willing to bet that you could poll the ECU on every pass though loop,() as well as polling the headlight switch, and get acceptable results, if you were NOT using delay() anywhere.

I used delay() to collect pulse for a period of time to calculate speed. Please note that when I run these two parts of codes separately (RPM and Speedo), they work without any problems and give me the correct values. But when I merge them together, the speedo value becomes incorrect.

econjack:
@vajira: Keep in mind that any call to delay() puts the board into a coma for the delay period you specify, so don't expect to "sense" anything happening during that period of time. If you really do need some kind of delay, check out the "Blink Without delay()" in the Examples section of the IDE.

Thank you for the reply econjack. What I understood was I shouldn't use delay() inside ISR function. Looks like I got it wrong.

The following code was designed to measure the pulse count with high accuracy and very fast return times, even for low RPM's.
It can measure right from 0 up to 40.000RPM (Yes, 40 Thousand) and has an hardware overflow that returns to zero in case a pulse isn't received in 1/3 second, for example if the encoder signal is lost.

   static uint16_t results; 
   static uint16_t old_results;
   static uint16_t time; 
   static uint16_t old_time=0;
   static uint16_t new_time = 0;
   static int i=0;                            // Timer 1 overflow counter 
    
void setup() {
 
        Serial.begin(9600);
     
	pinMode(8, INPUT);                  // ICP pin (digital pin 8 on Arduino) as input 
	TCCR1B = (1<<CS10);                 // Set timer 1 Clock source = FOsc
	TCCR1A = 0;                         // Normal counting mode 
	TIMSK1 |= _BV(ICIE1);               // enable input capture interrupt for timer 1
	TIMSK1 |= _BV(OCIE1A);              // Timer1 compare output interrupt enable
        EIMSK |= (1 << INT0);               // Enable external interrupt INT0 for power module fault detection
        EICRA |= (1 << ISC01);              // Trigger INT0 on falling edge
       


}

void loop() {
  
  Serial.print(results);
  Serial.println("pulses");
  delay(500);
}

   //ICR interrupt vector 
   ISR(TIMER1_CAPT_vect){  
       //Assign the value of the ICR1 Register to the variable results
       OCR1A = new_time = ICR1;          
       time = new_time-old_time;
       old_time=new_time;
       results = (16000000/(time+(65536*i)));
       //Save the Value of i into a variable a, so that calculations can be made outside the ISR  
       i=0;
       //Check if acceleration rate is within a reasonable range. 
       //If not the encoder signal may be corrupted
   }
    //Output compare interrupt
    ISR(TIMER1_COMPA_vect){ 
       //If less than a 260ms elapsed, increment i 
       if (i<123) 
       i++; 
       //else define the output as zero. 
       else 
       results=0;
       //i=0;
       }

You cannot use any other library that uses timer 1, including the PWM pins attached to timer 1 (I think are 9 and 10).

The result is returned in pulses per second. Divide that by the number of pulses your car ECU returns per rev (typically 2 for a 4 cylinder car) and multiply by 60 to get RPM. No float needed, as it happens with most libraries (Therefore ==== FAST). Input pin is ICP1 (pin8).

As to measure mileage. Set up timer two and set up a periodic 1 second delay, for example with the millis function.
Ever second record the value and compare the previously saved value with the current value. This will give you instantaneous speed in pulses per second. Then you need to figure out how many pulses correspond to a mile. For a VW car its about 6000 (1 mile traveled). From here I guess you can make the rest.

amvcs08:
The following code was designed to measure the pulse count with high accuracy and very fast return times, even for low RPM's.
It can measure right from 0 up to 40.000RPM (Yes, 40 Thousand) and has an hardware overflow that returns to zero in case a pulse isn't received in 1/3 second, for example if the encoder signal is lost.

   static uint16_t results; 

static uint16_t old_results;
  static uint16_t time;
  static uint16_t old_time=0;
  static uint16_t new_time = 0;
  static int i=0;                            // Timer 1 overflow counter
   
void setup() {

Serial.begin(9600);
   
pinMode(8, INPUT);                  // ICP pin (digital pin 8 on Arduino) as input
TCCR1B = (1<<CS10);                 // Set timer 1 Clock source = FOsc
TCCR1A = 0;                         // Normal counting mode
TIMSK1 |= _BV(ICIE1);               // enable input capture interrupt for timer 1
TIMSK1 |= _BV(OCIE1A);              // Timer1 compare output interrupt enable
       EIMSK |= (1 << INT0);               // Enable external interrupt INT0 for power module fault detection
       EICRA |= (1 << ISC01);              // Trigger INT0 on falling edge

}

void loop() {
 
 Serial.print(results);
 Serial.println("pulses");
 delay(500);
}

//ICR interrupt vector
  ISR(TIMER1_CAPT_vect){  
      //Assign the value of the ICR1 Register to the variable results
      OCR1A = new_time = ICR1;          
      time = new_time-old_time;
      old_time=new_time;
      results = (16000000/(time+(65536*i)));
      //Save the Value of i into a variable a, so that calculations can be made outside the ISR  
      i=0;
      //Check if acceleration rate is within a reasonable range.
      //If not the encoder signal may be corrupted
  }
   //Output compare interrupt
   ISR(TIMER1_COMPA_vect){
      //If less than a 260ms elapsed, increment i
      if (i<123)
      i++;
      //else define the output as zero.
      else
      results=0;
      //i=0;
      }



You cannot use any other library that uses timer 1, including the PWM pins attached to timer 1 (I think are 9 and 10).

The result is returned in pulses per second. Divide that by the number of pulses your car ECU returns per rev (typically 2 for a 4 cylinder car) and multiply by 60 to get RPM. No float needed, as it happens with most libraries (Therefore ==== FAST). Input pin is ICP1 (pin8).

As to measure mileage. Set up timer two and set up a periodic 1 second delay, for example with the millis function.
Ever second record the value and compare the previously saved value with the current value. This will give you instantaneous speed in pulses per second. Then you need to figure out how many pulses correspond to a mile. For a VW car its about 6000 (1 mile traveled). From here I guess you can make the rest.

Thanks for the reply. I tested your code and kept car's RPM at 900. But it returned values like
1227pulses
1334pulses
1239pulses
1275pulses
1132pulses
1175pulses
1284pulses
1184pulses
1107pulses
1219pulses
1087pulses
1269pulses
1243pulses
1032pulses
1098pulses
1202pulses
1174pulses
1030pulses
1216pulses
1560pulses
998pulses
1197pulses
1180pulses
1161pulses
13466pulses

Actually I had to divide the actual pulse count by 4 to get those numbers near to 900.

Serial.print(results/4);

Any idea why? If I can get a value like 900(+/-)25, could be better.

Well looks like this is not gonna work for me. I already spent days and finally gonna give up. :~ All these codes work well when run separately. But when I merge them together, one of them gives wrong values or stop working. May be something wrong with my logic and the flow of the coding as I'm new to Arduino.

vajira:
But can I use 2 interrupts to read both RPM and speedo pulses in the same way as you mentioned above?

That should work fine.

By the way ---- It's a good idea to quote a little bit of a Post to make it clear which one you are replying to but it makes the Forum difficult to read if you quote the full post every time.

...R

vajira:
Thanks for the reply. I tested your code and kept car's RPM at 900. But it returned values like
1227pulses
1334pulses
1239pulses
1275pulses
1132pulses
1175pulses
1284pulses
1184pulses
1107pulses
1219pulses
1087pulses
1269pulses
1243pulses
1032pulses
1098pulses
1202pulses
1174pulses
1030pulses
1216pulses
1560pulses
998pulses
1197pulses
1180pulses
1161pulses
13466pulses

Actually I had to divide the actual pulse count by 4 to get those numbers near to 900.

Serial.print(results/4);

Any idea why? If I can get a value like 900(+/-)25, could be better.

Dear friend, don't give yet...
How exactly is the arduino connected to the car? Voltage dividers and alike are needed, have you checked with an oscilloscope to see how the waveform looks like? I would suggest that the signal is reaching the arduino corrupted, but without a waveform and a schematic of how it is connected I cant give any hints.

How many cylinders is the car? Where is the signal being tapped? ECU? Alternator? Cam/crankshaft hall sensor?

The result will not correspond to the RPM, it will correspond to a given number of pulses and needs to be a square wave.
After you get an accurate pulse number, then you can convert to RPM.

God bless you all.

amvcs08:

How exactly is the arduino connected to the car?

Please refer to the attached image. The RPM signal is connected to arduino via a 2N3906 transistor.

How many cylinders is the car? Where is the signal being tapped? ECU? Alternator? Cam/crankshaft hall sensor?

Its a Toyota 4 cylinder engine. I took the tach signal from OBD1 port. (probably the connection coming from the Ignitor). Please check out this short video of my setup.
https://www.youtube.com/watch?v=l1aM1aSg-58

Also I took the VSS directly from the ECU. As my car manual says, it outputs a 5v square wave pulses.

The result will not correspond to the RPM, it will correspond to a given number of pulses and needs to be a square wave.
After you get an accurate pulse number, then you can convert to RPM.

When I use my first program codes, it works. You can see it on my video.

rpm.jpg

Use a 10K pull up from pin 8 to 5V on the arduino.

If it still doesn't work I would need to see a oscilloscope photo of your waveform.
My sketch uses edge triggered sampling, so it may be that the signal is not clean enough, or has some DC offset.

Best of luck :wink:

Hi amvcs08,

Use a 10K pull up from pin 8 to 5V on the arduino.

I already used it but the results are the same.

I dont have an oscilloscope for the moment as I started electronics recently. The signal sure is noisy as it comes from the ignitor.

My plan is to use my existing working code on main arduino board to read RPM. I have got few extra Atmega328 and may be I can use one of them to calculate speed and send it to main arduino via a serial link. So I can use the same display. Hopefully it will work. :smiley:

Thank you.