micros() on interrupt

Hi,

I'm trying to read rpm. I have an timer interrupt that enables interruptPin/code. Te interrupt code is:

void RPMISR(){
  if(RPMPulseCount == 0){
    RPMStartTime=micros();
    RPMPulseCount++;
    }
  else if(RPMPulseCount > 0 &&  RPMPulsesToMeasure > RPMPulseCount){
    RPMPulseCount++;
   } 
  else if(RPMPulseCount == RPMPulsesToMeasure) {
    detachInterrupt(digitalPinToInterrupt(RPMPin));
    RPMEndTime=micros();
    RPM=((1000000/((RPMEndTime-RPMStartTime)/(RPMPulseCount)))/TriggerToothCount)*60;
    RPMPulseCount=0;
    RPMStarted=false;
    RPMUpdated = true;
    if(debug==1){
      Serial.print("RPM: ");
      Serial.println(RPM);
      }
    }
}

Tis quite well works.. But.... If i enable the RPM reading with interrupt with 1ms interval it works quite well, but if i enable the RPM reading for example every 200ms the RPM is floating around and is not accurate. for some reason the micros() is not accurate if i run this interrupt code less often. Just cannot understand why.

Rule 1, NO serial print in ISR.

Rule 2, keep them short. Move all the calculation outside the ISR. Just store the time and set a flag and go out of it.

Rule 3, see Rule 1.

The first thing Arduino does in entering an ISR is to disable global interrupts until it exits the ISR. What this means is that anything that relies on an interrupt (millis, micros, Serial.print for example) will simply not work. The reality is that a single interrupt will be queued for execution upon re-establishing globals, but this is not something that should be taken into consideration, just be aware.

Thanks for the adevices.. I have now cleanend a bit of ISR. It looks now like this:

void RPMISR(){
  if(RPMPulseCount == 0){
    RPMStartTime=micros();
    RPMPulseCount++;
    }
  else if(RPMPulseCount > 0 &&  RPMPulsesToMeasure > RPMPulseCount){
    RPMPulseCount++;
   } 
  else if(RPMPulseCount == RPMPulsesToMeasure) {
    RPMEndTime=micros();
    detachInterrupt(digitalPinToInterrupt(RPMPin));
    RPMUpdated = true;
    
    }
}

But still, I'm testing with arduino leonardo's pwm output that is 1khz signal and i cannot get accurate results:

Hz: 1013.51
delta time: 5920.00micros
Hz: 1142.42
delta time: 5252.00micros
Hz: 1070.66
delta time: 5604.00micros
Hz: 1160.99
delta time: 5168.00micros
Hz: 1086.17
delta time: 5524.00micros
Hz: 1021.80
delta time: 5872.00micros
Hz: 1153.85
delta time: 5200.00micros

should i still have to lighten up the ISR?

Why are you detaching the interrupt handler in the interrupt handler?

What this means is that anything that relies on an interrupt (millis, micros, Serial.print for example)

Does micros() depend on an interrupt ?

UKHeliBob:
Does micros() depend on an interrupt ?

Yes, if interrupts are disabled the rollover/overflow of the 8 bit timer register will not be handled. This means that micros() will only have a reliable range of 0..(256*X)-1 where X is 4 on 16MHz and 8 on 8Mhz. Because micros() may register a single rollover, the timer register can extend to 9 bits giving an unreliable range of up to twice the reliable range.

EDIT: The following untested code should deadlock an arduino:

void setup()
{
  Serial.begin(9600);
  unsigned long MAX = 256 * ((F_CPU == 16000000UL) ? 4 : 8) * 2;
  noInterrupts();
  unsigned long us = micros();
  while (micros() - us < MAX) ;
  interrupts();
}

void loop()
{
  Serial.println("You will never see this!");
  delay(1000);
}

EDIT: Code has been updated after UKHeliBob tested it.

PaulS:
Why are you detaching the interrupt handler in the interrupt handler?

Because in that point I have got enough pulses to calculate RPM. There is no need to let it run anymore before i want to have new RPM value. The I will again enable the interrupt pin.

As i understand that should not interfere micros because i'm not disabling all interrupts. Just the RPM interruptPIN

Because in that point I have got enough pulses to calculate RPM. There is no need to let it run anymore before i want to have new RPM value. The I will again enable the interrupt pin.

That's like removing your doorbell because you are going on vacation.

Let the mailman ring the doorbell while you are not there, or don't care.

When you are ready to measure RPM again, just disable interrupts, set the count to 0, and enable interrupts. Meanwhile, let the interrupt happen uselessly.

1 Like

The following untested code should deadlock an arduino:

Just tested it on a Nano and got the following

You will never see this!
You will never see this!
You will never see this!
You will never see this!
You will never see this!
and so on

PaulS:
That's like removing your doorbell because you are going on vacation.

Let the mailman ring the doorbell while you are not there, or don't care.

When you are ready to measure RPM again, just disable interrupts, set the count to 0, and enable interrupts. Meanwhile, let the interrupt happen uselessly.

Damn, You are good...

Now delta time changes max of 4micros in every measurement..

delta time: 2800.00micros
Hz: 2145.92
delta time: 2796.00micros
Hz: 2145.92
delta time: 2796.00micros
Hz: 2142.86
delta time: 2800.00micros
Hz: 2145.92
delta time: 2796.00micros
Hz: 2142.86
delta time: 2800.00micros
Hz: 2142.86
delta time: 2800.00micros
Hz: 2145.92
delta time: 2796.00micros
Hz: 2145.92
delta time: 2796.00micros
Hz: 2142.86
delta time: 2800.00micros
Hz: 2145.92
delta time: 2796.00micros
Hz: 2142.86
delta time: 2800.00micros

So that looks awesome...

Damn, You are good...

I knew that. 8)

Now it works, but can some one explain why detaching that interrupt PIN makes that error to micros?

This, on the other hand, prints 1608.

#include    <util/delay.h>

void setup()
{
    Serial.begin(38400);
    uint32_t go, nogo;
    go = micros();
    cli();
    _delay_ms(1000);
    sei();
    nogo = micros();
    Serial.println((nogo - go));
}

when this prints 1006080

    //cli();
    _delay_ms(1000);
    //sei();

UKHeliBob:
Just tested it on a Nano and got the following

You will never see this!

You will never see this!
You will never see this!
You will never see this!
You will never see this!
and so on

Change "MAX" to "1600" or "256 * ( (F_CPU == 16000000UL) ? 4 : 8 ) * 2" - this will deadlock my nano. Actually I dunno why it counts out of bounds, but I'd sure like to know :slight_smile:

In case anyone should be interrested, I figured out that micros() may in fact register a single rollover of the timer register even if interrupts are disabled or if used in an interrupt handler. This will - unreliably - extend the range that micros() will cover by up to a multiple of 2 :slight_smile: I've edited my previous post with the deadlock example for clarification.