faster hardware interrupt?

Hello,
Plateform : arduino Nano
In my applcation I need a very fast hardware interrupt that call a routine. My test and this extract form Gammon Forum : Electronics : Microprocessors : Interrupts

says “I count 82 cycles there (5.125 µS in total at 16 MHz) as overhead plus whatever is actually done in the supplied interrupt routine.” I real life I actally measure 10µs on my oscilloscope, this is too much in my case.

Question: I read somewhere (don’t know where) that there is a trick to go faster using a like using a serial interrupt or something like that use a different timer?

Or I guess the only option left is using a microcontroller faster than 16Mhz…

The timing given in nick post are for external interrupts using the IDE's attachinterrupt()

If you want to get rid of that overhead just use the interrupt directly.

Mark

roscodemars: I real life I actally measure 10µs on my oscilloscope, this is too much in my case.

In real life using what code?

Using this code:

ISR (INT0_vect)
{
PINB = bit (5); // toggle D13
}  // end of ISR

void setup ()
{
  pinMode (13, OUTPUT);
  EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
  EICRA |= bit (ISC01);    // set wanted flags (falling level interrupt)
  EIMSK |= bit (INT0);     // enable it  
}  // end of setup

void loop () { }

On the oscilloscope I measure 1.46 µS between the incoming pulse and the ISR toggling pin 13. On the logic analyzer I get 1.35 µS (it may well have been a different pulse).

This is perfectly consistent with my page ( Gammon Forum : Electronics : Microprocessors : Interrupts ) section “How long does it take to execute an ISR?” where I said:

So from the moment the interrupt occurs, to the first line of code being executed, would be 16 + 7 cycles (23 cycles), at 62.5 nS per clock cycle, that would be 1.4375 µS. That’s assuming a 16 MHz clock.

If you are getting 10 µS then you are doing something differently.

In my applcation I need a very fast hardware interrupt that call a routine.

Why do you need that?

[quote author=Nick Gammon link=topic=273225.msg1925643#msg1925643 date=1413603877] Why do you need that? [/quote] Always the question: "Say what you want to do, not how you imagine doing it." :D

If your code has nothing else to do then polling is the fastest way to respond to an event.

But as has been said, tell us what needs to be done.


Rob

Thanks you for your answers, I really appricate your support. Here what I’m trying to achieve:

The application is a system to control the ignition timing of an engine. In chronoloical order here what I would like to do for each engine cycle:

1-I receive on D2 a trigger IN coming form a hall sensor, and starting and hardware interrupt
2-The hardware interrupt set a timer interrupt that will be the time to wait before the trigger OUT (spark), this time to wait will change at each cycle.
3- waiting for the time interrupt, I can compute next cycle timing
4- I can do a lower priority task like sending data out thru the serial interface

Constraints:
I need a 1 degree accuracy at 15000rpm it means that 1 and 2 must be done at max of 11µs

In my previous attempts I gave up using interrupts as it was too long but my code ended up being too complex to maintain (I plan to put it ompen source) and at that time I did not have to do task 4 (send serial data out).
you were right I messed up my measurement, I took back my notes here what I posted long ago: http://forum.arduino.cc/index.php?topic=244510.msg1750810#msg1750810
With the hardware interrupt , takes 3.47µs (time between the LOW state on pin 2 and the ouput going to HIGH on pin 5

void setup() {

  pinMode(2, INPUT);
  pinMode(5, OUTPUT);
  
  attachInterrupt(0, inter, FALLING);
}


void inter (void)
{
      PORTD |= 1 << 5; //fast digitalwrite HIGH on output 5
      PORTD &= ~(1 << 5); //fast digitalwrite LOW on output 5 
}

void loop() {
  // do nothing else than waiting for interrupts

Question:
Now I want to try to simulate 1 & 2 to measure the timining needed to process, with a sheduled interrupt timer of 1µs, I’m going to do it tonite, this is the code I have not tested yet, let me know if you find someting wrong, I’m not fmiliar with the prescaler, I’ll give it a try.
I want the cleanest code as possible, I anticipating to be too slow on a nano to be usable, then I will try and compare it with a teensy 3.1 at 96Mhz

void setup() {
  pinMode(2, INPUT);
  pinMode(5, OUTPUT);
  attachInterrupt(0, inter, FALLING);
  Serial.begin(115200);
}


void inter (void)
{
  //set a timer that will trigger only once and change of frequecy at each cycle


  cli();//stop interrupts

  //set timer1 interrupt at 1Mhz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1Mhz increments
  //This will change at each cycle
  OCR1A = 15; // = (16*10^6) / (1*10^6) - 1
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 presaceler 1
  TCCR1B |=  (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  sei();//allow interrupts
}

//this enables the trigger OUT 
ISR(TIMER1_COMPA_vect){
  PORTD |= 1 << 5; //fast digitalwrite HIGH on output 5
  delayMicroseconds(1);
  PORTD &= ~(1 << 5); //fast digitalwrite LOW on output 5 

    TIMSK1 |= (1 << OCIE1A);   //disable timer as it needs to be triggered only once

}


void loop() 
{
  Serial.print("Background low priority task");
  delay(500);

}

You don't need the sei/cli() in an ISR, that's handled by the hardware. And I'm pretty sure most of that timer setup only needs to be done once.


Rob

Graynomad I do need to set a different timer at each cycle. Frequency of the timer will be calculated in the main loop at each cycle depending on other paramaters I did not exposed it here to simplify the code.

I can see that OCR1A needs to be set every time, but surely not the control and interrupt regs.


Rob

roscodemars: I need a 1 degree accuracy at 15000rpm it means that 1 and 2 must be done at max of 11µs

I wonder if this is logically correct.

There is a difference between the accurate timing of when a spark happens and the length of time in which the spark can be "planned"

You have most of the time between one spark and the next (about 4000 usecs) to figure out exactly when the spark will happen.

If you really do need things to happen every 11 usecs then I suspect you are running too close to the maximum capacity of a 16MHz Arduino.

...R

I had something almost identical to that on my page:

http://www.gammon.com.au/forum/?id=11488&reply=3#reply3

As I said above, if you want faster response to the initial interrupt, use the "ISR (INT0_vect)" concept rather than attachInterrupt. That saves a couple of microseconds.

What puzzles me here, though is that you want a fast response to an interrupt, but then immediately introduce a delay. Surely the delay can be shortened by the interrupt lag? And as Graynomad said, lose the sei() cli() calls from your ISR.

Can you specify how long a delay you want for your ignition timing? The code I posed on my interrupts page (the ignition timing one) appeared to work OK.

Since you are introducing a delay anyway, all you are worried about is interrupt "jitter" not the time it takes for the ISR to fire. For example, if you know it takes 2 µS for the interrupt to be processed, you subtract 2 µS from the time you delay for.

The jitter should be only a couple of microseconds (depending on whether a timer interrupt has fired for the millis() timer) at most. And you can always turn that interrupt off and check for the timer overflow yourself.

I need a 1 degree accuracy at 15000rpm it means that 1 and 2 must be done at max of 11µs

And you are saying you can tolerate 11 µS jitter.

Another technique you can use to eliminate jitter is to use the input capture unit. This remembers in a hardware register when a timer event occurs. There is an example here:

http://www.gammon.com.au/forum/?id=11504&reply=12#reply12

Also for a capacitor measurer I did:

http://www.gammon.com.au/forum/?id=12075

From the datasheet:

The Timer/Counter incorporates an Input Capture unit that can capture external events and give them a time-stamp indicating time of occurrence.

Using that you can "remember" exactly when the interrupt event occurred. Now since you are introducing a delay anyway, the exact time the ISR is entered is not critical. You read the input capture unit (ICR), find when the interrupt occurred (how many microseconds ago) and build that into your delay calculations.

I’ve modified my ignition timing example a bit, based on the above comments.

// Timer with an ISR example
// Author: Nick Gammon
// Date: 13th January 2012
// Modified: 19 October 2014

#include <digitalWriteFast.h>

const byte FIRE_SENSOR = 2;  // note this is interrupt 0
const byte SPARKPLUG = 9;

volatile unsigned int sparkDelayTime = 500;  // microseconds
volatile unsigned int sparkOnTime = 2000;     // microseconds

// allow for time taken to enter ISR (determine empirically)
const unsigned int isrDelayFactor = 4;        // microseconds

// is spark currently on?
volatile boolean sparkOn;

void activateInterrupt0 ()
  {
  EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
  EICRA |=  bit (ISC01);    // set wanted flags (falling level interrupt)
  EIFR   =  bit (INTF0);    // clear flag for interrupt 0
  EIMSK |=  bit (INT0);     // enable it
  }  // end of activateInterrupt0

void deactivateInterrupt0 ()
  {
  EIMSK &= ~bit (INT0);     // disable it
  }  // end of deactivateInterrupt0

// interrupt for when time to turn spark on then off again
ISR (TIMER1_COMPA_vect)
  {

  // if currently on, turn off
  if (sparkOn)
    {
    digitalWriteFast (SPARKPLUG, LOW);  // spark off
    TCCR1B = 0;                      // stop timer
    TIMSK1 = 0;                      // cancel timer interrupt
    activateInterrupt0 ();           // re-instate interrupts for firing time
    }
  else
    // hold-off time must be up
    {
    digitalWriteFast (SPARKPLUG, HIGH);   // spark on
    TCCR1B = 0;                        // stop timer
    TCNT1 = 0;                         // count back to zero
    TCCR1B = bit(WGM12) | bit(CS11);   // CTC, scale to clock / 8
    // time before timer fires (zero relative)
    // multiply by two because we are on a prescaler of 8
    OCR1A = (sparkOnTime * 2) - (isrDelayFactor * 2) - 1;     
    }
    
  sparkOn = !sparkOn;  // toggle

  }  // end of TIMER1_COMPA_vect

// ISR for when to fire
ISR (INT0_vect)
  {
  sparkOn = false;                  // make sure flag off just in case

  // set up Timer 1
  TCCR1A = 0;  // normal mode
  TCNT1 = 0;   // count back to zero
  TCCR1B = bit(WGM12) | bit(CS11);  // CTC, scale to clock / 8
  // time before timer fires - zero relative
  // multiply by two because we are on a prescaler of 8
  OCR1A = (sparkDelayTime * 2) - (isrDelayFactor * 2) - 1; 
  TIMSK1 = bit (OCIE1A);            // interrupt on Compare A Match

  deactivateInterrupt0 ();          // no more interrupts yet4
  } // end of ISR (INT0_vect)

void setup() 
  {
  TCCR1A = 0;  // normal mode
  TCCR1B = 0;  // stop timer
  TIMSK1 = 0;   // cancel timer interrupt

  pinMode (SPARKPLUG, OUTPUT);
  pinMode (FIRE_SENSOR, INPUT_PULLUP);
  
  activateInterrupt0 ();
  }  // end of setup

void loop() 
  { 
  // read sensors, compute time to fire spark
  
  if (false)  // if we need to change the time, insert condition here ...
    {
    noInterrupts ();        // atomic change of the time amount
    sparkDelayTime = 500;  // delay before spark in microseconds
    sparkOnTime = 2000;     // spark on time in microseconds
    interrupts ();
    }
  }  // end of loop

This has a few improvements. First, it uses ISR (INT0_vect) for the initial interrupt, as that will be serviced more quickly. Second I added a “subtract 1” from the timer amounts, as the timers are zero-relative. Third, I used digitalWriteFast to turn the ignition on and off, which is faster than digitalWrite.

Finally there is a “tweak” figure of 4 µS which you can adjust, which compensates for the time it takes for the ISR to actually do its thing. Using that code, I measured about a half-microsecond error in the timings on the logical analyzer.

Proof:

The difference between T1 and T2 is 499.5 µS which is only 500 nS out from the requirement. The gap with the arrows (width) is 1998.6 µS which is only 1.4 µS too low.

Nick,

Quote I need a 1 degree accuracy at 15000rpm it means that 1 and 2 must be done at max of 11µs

And you are saying you can tolerate 11 µS jitter.

Yes this is exactely it!

I'm amazed to read your sparkplug code it looks so similar to my production version, but just way cleaner and smarter, it's a piece of art. :)

Also, your quote if it happend to be usable in my case, is just going to make my code so much faster :

The Timer/Counter incorporates an Input Capture unit that can capture external events and give them a time-stamp indicating time of occurrence.

because today I have to store some heavy long time micros()

Can you specify how long a delay you want for your ignition timing?

The delay could vary from the minimum possible (7µs tested in your code) to 150ms Logic behind all that is that sparkDelayTime (the time before the spark) is a function depending on the current frequencies of pulse received on the hall sensor, and in some extreme case at low RPM (=long periods between 2 sensor input) the time to wait could represent almost 99% of this period.

I will let you know the performances of the finalized code, I plan to test it on Nano, Rfduino and Teensy 3.1, but I'm sure that for low level instructions like these I will need some time adapt the code.

Nick,

In your code "Timing an interval using the input capture unit" you are using D8 as a capture Input:

TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)

Is it possible to do the same for a Falling on D2? Indeed I'm trying to find an efficient way to know the frequency of signal of the hall sensor.

all Nick's code here:

http://www.gammon.com.au/forum/?id=11504&reply=12#reply12

From the datasheet:

The main trigger source for the Input Capture unit is the Input Capture pin (ICP1). Timer/Counter1 can alternatively use the Analog Comparator output as trigger source for the Input Capture unit. The Analog Comparator is selected as trigger source by setting the Analog Comparator Input Capture (ACIC) bit in the Analog Comparator Control and Status Register (ACSR). Be aware that changing trigger source can trigger a capture. The Input Capture Flag must therefore be cleared after the change.

The pin ICP1 is pin 14 on the chip, or D8 on the board. Anyway, although I know I mentioned it, since my test above shows you can get within 500 nS without the input capture unit, I wouldn't worry too much about it.

I'm trying to find an efficient way to know the frequency of signal of the hall sensor D2. Today in my code I have to record the current time when the D2 interrupt happens and substract it with the previous cycle timing. I was wondering if there was a better way to do it.

With a quick bit of maths, taking your figure of 15000 RPM (or actually rounding it up to a nice 18000 RPM)

This equates to 300 Revs per second. Which is a total of 108,000 Degrees per second. This gives a time of about 9.25 microseconds per degree.

Isn't it normal to have the sensor set about 10 degrees before you need to do something (ie giving you about 925 microseconds to get things organised?

Isn't it normal to have the sensor set about 10 degrees before you need to do something (ie giving you about 925 microseconds to get things organised?

In my case the sensor IN position can't be moved, and the trigger OUT could happen between 11µs and almost 4ms after the sensor IN interrupt.