Interrupt Delay 100us

I'm trying to make a delay circuit for my car to change the charge time for the iginition coils. The signal is low and then goes high for 5ms to charge the coils, when the signal goes low again it fires the coil. The new coils I have need a 3ms charge time so I plan to delay the charge signal by 2ms. That falling edge to fire is very important. But, I'm having trouble getting really tight timing despite using interrupts. Any help you can offer would be greatly appreciated.

First the hardware, I'm using a ATMEGA8 @ 8MHz with no XTAL or Bootloader. Here is my Board.txt settings.

atmega8.name=Arduino NG or older w/ ATmega8

atmega8.upload.using=usbtinyisp

atmega8.upload.protocol=stk500 atmega8.upload.maximum_size=7168 atmega8.upload.speed=19200

atmega8.bootloader.low_fuses=0xd4 atmega8.bootloader.high_fuses=0xc4 atmega8.bootloader.path=atmega8 atmega8.bootloader.file=ATmegaBOOT.hex atmega8.bootloader.unlock_bits=0x3F atmega8.bootloader.lock_bits=0x0F

atmega8.build.mcu=atmega8 atmega8.build.f_cpu=8000000L atmega8.build.core=arduino

My first program is just following the input, so I could test the hardware, 47uf Cap, .1uf Cap, NPN transistors, and a 74HC7014N buffer chip. I'm using 5ms square wave to signal the Mega8. According to my scope the output from the Mega8 is about 100uS behind the pulse. I would expect maybe a couple of uS but 100 is bad for this project. It would throw my timing off.

Here is my Code for the Mega8:

const int OutputCOPA = 5; const int OutputCOPB = 8;

const int InputCOPA = 2; const int InputCOPB = 3; void setup() { // put your setup code here, to run once:

pinMode(OutputCOPA, OUTPUT); pinMode(OutputCOPB, OUTPUT);

pinMode(InputCOPA, INPUT); pinMode(InputCOPB, INPUT);

attachInterrupt(0, int1, CHANGE);//pin2 attachInterrupt(1, int2, CHANGE); // pin3

digitalWrite(OutputCOPA,HIGH); digitalWrite(OutputCOPB,HIGH);

}

void loop() { //Not Used Yet }

void int1(){ Timer1 = micros(); if (digitalRead(InputCOPA) == true){ digitalWrite(OutputCOPA,LOW); } else { digitalWrite(OutputCOPA,HIGH); }

}

void int2(){ Timer2 = micros(); if (digitalRead(InputCOPA) == true){ digitalWrite(OutputCOPB,LOW); } else { digitalWrite(OutputCOPB,HIGH); } }

with no XTAL

The onboard clock has poor precision. If you need precise and accurate timing, you'll also need an external crystal to provide a precise and accurate clock to base that timing on.

If you want things to happen quickly, you must get away from using digitalRead() and/or digitalWrite() in your interrupts. These functions do sanity checks in the background (or 'behind the scenes' as some other members might find more fitting), which takes precious time.

Look at 'direct port manipulation'. You'll have to become friends with the 'PIN' and 'PORT' registers. It's not difficult at all.

Setting/clearing an output that way only takes a few clock cycles (definitely less than 4µs at 16MHz core clock).

So, I wrote a final version of what I hoped would be my code, but I think it is still too slow.

After everything the falling time on the ISR is still about 75-50us. I don’t know how to make it go any faster I tried using a 328 @ 16MHz and that didn’t seem to help at all. Am I doing something wrong? Do you guys think if I did it in AVR Studio it would be faster?

Does anyone have a digital scope they could look at the waveform and give me an exact number of how long it is taking?

#define PB7 7
#define PB6 6
#define PB5 5
#define PB4 4
#define PB3 3
#define PB2 2
#define PB1 1
#define PB0 0

#define PD7 7
#define PD6 6
#define PD5 5
#define PD4 4
#define PD3 3
#define PD2 2
#define PD1 1
#define PD0 0

#define PIN2Input (PIND & (1<<PD2)) != 0 //Input Port 2
#define PIN3Input (PIND & (1<<PD3)) != 0 //Input Port 3

#define OutputAHigh PORTD |= _BV(PD5) //Output 5 High
#define OutputALow PORTD &= ~_BV(PD5) //Output 5 Low

#define OutputBHigh PORTB |= _BV(PB0) //Output 8 High
#define OutputBLow PORTB &= ~_BV(PB0) //Output 8 Low

void setup() {

//Declair I/O
pinMode(5, OUTPUT);
pinMode(8, OUTPUT);

pinMode(2, INPUT);
pinMode(3, INPUT);

attachInterrupt(0, int1, FALLING); //pin2
attachInterrupt(1, int2, FALLING); // pin3

//Make sure the Transistors go low
OutputAHigh;
OutputBHigh;

}

unsigned long Timer1 = 0; //Set Millis to 0
unsigned long Timer2 = 0; //Set Millis to 0

bool PinATimerState = false; //Set Timers A to Not Set
bool PinBTimerState = false; //Set Timers B to Not Set

void loop() {

if (PIN2Input && PinATimerState == false){ //See if the Port is High and Time Not Set
Timer1 = micros(); //Capture the Time
PinATimerState = true; //Time is Set
}

if (PIN3Input && PinBTimerState == false){ //See if the Port is High and Time Not Set
Timer2 = micros(); //Capture the Time
PinBTimerState = true; //Time is Set
}

if ( PIN2Input && ((micros() - Timer1) > 1500)){ //After 1500 Millis
OutputALow;
} else {
OutputAHigh;
}

if ( PIN3Input && ((micros() - Timer2) > 1500)){
OutputBLow;
} else {
OutputBHigh;
}

}

void int1(){
OutputAHigh;
PinATimerState = false;
}

void int2(){
OutputBHigh;
PinBTimerState = false;
}

If it takes too long until the ISR code gets called, you may be running into overhead again. attachInterrupt() is a high level function as well and your code gets called via function pointers, not directly. There will still be a small overhead left nevertheless, you won't get it down to 0.

If you need it quick, write your own interrupt handlers and set the registers that govern the external interrupts (int0, int1) yourself.

http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Have a look at the datasheet as well for which registers to tweak (EICRA, EIMSK ...)

A logic analyzer would be a great idea. I can recommend the 'openbench logic sniffer' for 50$.

kingofl337: According to my scope the output from the Mega8 is about 100uS behind the pulse. I would expect maybe a couple of uS but 100 is bad for this project. It would throw my timing off.

My measurements don't confirm that at all (on an Atmega328P running at 16 MHz).

As you can see, the time for the ISR to react is 3.5 uS, which is what I would expect. That is about the normal setup time to enter an ISR (the ones supplied by attachInterrupt anyway, which spend quite a bit of time pushing registers).

However you can see from the "Width" line that pin 5 is only kept high for about 5.5 uS, a lot less than the 5 mS you are talking about.

So your problem IMHO is the code that does the timing, not the reaction time of the ISR.

I don't quite get your logic in loop. Where does it measure 5 mS anyway?

You can see from the screenshot that it brings pin 5 high for 5.5 uS, then low again for 1.6 uS then high again, and then eventually low again (not visible) after 172 mS.

This might work better for you:

#define PB7     7
#define PB6     6
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

#define PD7     7
#define PD6     6
#define PD5     5
#define PD4     4
#define PD3     3
#define PD2     2
#define PD1     1
#define PD0     0

inline void OutputAHigh ()
{
  PORTD |=  _BV(PD5); 
}

inline void OutputALow ()
{
  PORTD &= ~_BV(PD5); 
}

inline void OutputBHigh ()
{
  PORTB |=  _BV(PB0); 
}

inline void OutputBLow ()
{
  PORTB &= ~_BV(PB0); 
}

void setup() {

  pinMode(5, OUTPUT); 
  pinMode(8, OUTPUT);

  attachInterrupt(0, int1, FALLING); // pin2
  attachInterrupt(1, int2, FALLING); // pin3

  //Make sure the Transistors go low
  OutputALow ();
  OutputBLow ();

}  // end of setup


volatile unsigned long int1Timer;  
volatile unsigned long int2Timer;  
volatile boolean int1Fired;
volatile boolean int2Fired;
volatile boolean pulsed1;
volatile boolean pulsed2;

void loop() 
{
unsigned long now = micros ();

  if (int1Fired && (now - int1Timer) > 2000L)  // 2 mS up?
  {
    int1Fired = false;
    OutputAHigh ();
    pulsed1 = true;
  }  // end of if ISR 1 fired

  if (pulsed1 && (now - int1Timer) > 5000L)  // 5 mS up?
  {
    pulsed1 = false;
    OutputALow ();
  } // end of if time to drop output A again

  if (int2Fired && (now - int2Timer) > 2000L)  // 2 mS up?
  {
    int2Fired = false;
    OutputBHigh ();
    pulsed2 = true;
  }  // end of if ISR 2 fired

  if (pulsed2 && (now - int2Timer) > 5000L)  // 5 mS up?
  {
    pulsed2 = false;
    OutputBLow ();
  }  // end of if time to drop output B again
  
}  // end of loop

// ISR 1
void int1(){
  if (!int1Fired)
  {
    int1Timer = micros ();
    pulsed1 = false;
    int1Fired = true;
  }  // end if not already fired
}  // end ISR 1

// ISR 2
void int2(){
 if (!int2Fired)
  {
    int2Timer = micros ();
    pulsed2 = false;
    int2Fired = true;
  } // end if not already fired
}  // end ISR 2

Measured performance:

As you can see, there is a delay of 2 mS after the interrupt before the pin is brought high, and then 3 mS later it is brought low again.

These functions do sanity checks in the background, which takes precious time.

In the background? The Arduino is a single-threaded, single-process machine. There is no background process running.

"Under the covers" or "behind the scene" might fit, but not "in the background".

You know exactly what I mean XD

"Not obvious/hidden from to the common user" is what I meant. And as I'm not a native speaker, I'm allowed no! entitled to a certain contingent of misunderestimations regarding the language. If G.W.B. got away with it, so can I!

Nick, thank you for the example codes.

Why is a function better then a #define?

I don't see in the code were it looks for the signal to go high to start the charge. The falling timing is critical but the spacing between firings is important as is it ignition timing and varies based on RPM.

The signal is as such the Engine Computer Computes RPM and Timing angle and when to fire, before firing the Signal goes high for 5ms to charge the igition coils and then goes low to iginte the fuel. The 5ms can also vary +/- 1ms based on engine load. I'm looking to deduct 2ms from the charge time. The charge time can be off +/- 200uS but the transition to low must be as close to perfect as possible as not to effect the spark timing.

Why is a function better then a #define?

I just found this confusing to look at:

OutputALow;

It looks like you left off the brackets for a function call, which would be:

OutputALow ();

An inline function should compile to efficient code, using #define does text substitution of the thing, I would prefer not to use it there.

I don't see in the code were it looks for the signal to go high to start the charge.

Your code only looked for a falling edge on the interrupt, so I assumed it wasn't critical. Perhaps if you describe in greater detail exactly your requirements?

eg.

pin 2 goes low, bring pin 5 high after 2 ms pin 2 goes high, bring pin 5 low again

Something like that, then we can meet those requirements.

I also changed this:

//Make sure the Transistors go low
OutputAHigh;
OutputBHigh;

I don't know if the comment was wrong, the code wrong, or bringing output A high makes the transistor go low, but it looks confusing.

//Make sure the Transistors go low
OutputAHigh;
OutputBHigh;

This is correct as the outputs are NPN's pulled high, they output the opposite of the input.

The processes is: 1. x amount of time passes (input Low, Output High) 2. Input goes high, Output stays high 3. 2ms passes since input when high, output goes low 4. Input goes low, output goes high (time critcal) repeat

This happens on two channels

After quite a bit of mucking around I got it to work as best as I think you can.

#define PB7     7
#define PB6     6
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

#define PD7     7
#define PD6     6
#define PD5     5
#define PD4     4
#define PD3     3
#define PD2     2
#define PD1     1
#define PD0     0

#define DELAY_TIME 2000L

volatile unsigned long int1Timer;  
volatile unsigned long int2Timer;  
volatile boolean coil1Fired;
volatile boolean coil2Fired;
volatile byte outputAstate;
volatile byte outputBstate;


inline void OutputAHigh ()
{
  PORTD |=  _BV(PD5);   // pin 5 on
  outputAstate = HIGH;
}

inline void OutputALow ()
{
  PORTD &= ~_BV(PD5);   // pin 5 off
  outputAstate = LOW;
}

inline void OutputBHigh ()
{
  PORTB |=  _BV(PB0);  // pin 8 on
  outputBstate = HIGH;
}

inline void OutputBLow ()
{
  PORTB &= ~_BV(PB0);  // pin 8 off
  outputBstate = LOW;
}

void setup() {

  pinMode (5, OUTPUT); 
  pinMode (8, OUTPUT); 

  attachInterrupt(0, isr1, CHANGE); // pin 2
  attachInterrupt(1, isr2, CHANGE); // pin 3

  //Make sure the Transistors go low (pull NPNs high to ground them)
  OutputAHigh (); 
  OutputBHigh (); 

}  // end of setup

void loop() 
{
  if (coil1Fired && (micros () - int1Timer) > DELAY_TIME)  // 2 mS up?
    {
    OutputALow (); 
    coil1Fired = false;
    }  // end of if ISR 1 fired

  if (coil2Fired && (micros () - int2Timer) > DELAY_TIME)  // 2 mS up?
    {
    OutputBLow (); 
    coil2Fired = false;
    }  // end of if ISR 1 fired

}  // end of loop


void isr1(){
  if (outputAstate == HIGH)
    // rising
    {
    int1Timer = micros ();
    coil1Fired = true;   
    }
  // falling
  else
    {
    OutputAHigh ();
    coil1Fired = false;   
    }

}  // end of ISR


void isr2(){
  if (outputBstate == HIGH)
    // rising
    {
    int2Timer = micros ();
    coil2Fired = true;   
    }
  // falling
  else
    {
    OutputBHigh ();
    coil2Fired = false;   
    }

}  // end of ISR

Overall results:

That shows that the two channels are working alongside. For testing I fed a square wave with a period of 7 mS into pin 2, and 8 mS into pin 3.

You can see that the T1 - T2 time is 2.018 mS, so the charging starts around the 2 mS that you specified after the pin goes high.

Zooming in on the critical part where the signal on pin 2 goes low:

There is a 3.9 uS delay before the corresponding transistor is switched. That isn't too bad, considering that an ISR takes about 3.5 uS to enter.

You can shave about 0.8 uS off the time to switch by directly writing the ISR, rather than using attachInterrupt, as that saves looking up the interrupt function in a table. I’m not sure if it is worth it, perhaps it is:

#define PB7     7
#define PB6     6
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

#define PD7     7
#define PD6     6
#define PD5     5
#define PD4     4
#define PD3     3
#define PD2     2
#define PD1     1
#define PD0     0

#define DELAY_TIME 2000L

volatile unsigned long int1Timer;  
volatile unsigned long int2Timer;  
volatile boolean coil1Fired;
volatile boolean coil2Fired;
volatile byte outputAstate;
volatile byte outputBstate;


inline void OutputAHigh ()
{
  PORTD |=  _BV(PD5);   // pin 5 on
  outputAstate = HIGH;
}

inline void OutputALow ()
{
  PORTD &= ~_BV(PD5);   // pin 5 off
  outputAstate = LOW;
}

inline void OutputBHigh ()
{
  PORTB |=  _BV(PB0);  // pin 8 on
  outputBstate = HIGH;
}

inline void OutputBLow ()
{
  PORTB &= ~_BV(PB0);  // pin 8 off
  outputBstate = LOW;
}

void setup() {

  pinMode (5, OUTPUT); 
  pinMode (8, OUTPUT); 

  // enable interrupt 0

  EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (CHANGE << ISC00);
  EIMSK |= (1 << INT0);

  // enable interrupt 1

  EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (CHANGE << ISC10);
  EIMSK |= (1 << INT1);

  //Make sure the Transistors go low (pull NPNs high to ground them)
  OutputAHigh (); 
  OutputBHigh (); 

}  // end of setup

void loop() 
{
  if (coil1Fired && (micros () - int1Timer) > DELAY_TIME)  // 2 mS up?
    {
    OutputALow (); 
    coil1Fired = false;
    }  // end of if ISR 1 fired

  if (coil2Fired && (micros () - int2Timer) > DELAY_TIME)  // 2 mS up?
    {
    OutputBLow (); 
    coil2Fired = false;
    }  // end of if ISR 1 fired

}  // end of loop

ISR(INT0_vect) {
 if (outputAstate == HIGH)
    // rising
    {
    int1Timer = micros ();
    coil1Fired = true;   
    }
  // falling
  else
    {
    OutputAHigh ();
    coil1Fired = false;   
    }
}


ISR(INT1_vect){
  if (outputBstate == HIGH)
    // rising
    {
    int2Timer = micros ();
    coil2Fired = true;   
    }
  // falling
  else
    {
    OutputBHigh ();
    coil2Fired = false;   
    }

}  // end of ISR

Don't call micros(), its slow. Use delayMicroseconds() which is both pretty accurate and fast down to about 1 or 2us. You should get pretty decent performance from just polling:

  while (true)
  {
    while (!(PIND & INMASK))
    {}
    while (PIND & INMASK)
    {}
    delayMicroseconds (15) ;
    PORTD &= OUTMASK ;
    .....
  }

or something like that - the overhead of a while loop is probably less than an interrupt I suspect.

If the timer0 interrupt is running (needed for micros(), millis ()) then you will get interrupt derived jitter - you might want to turn this off.

He wants to control two independent things. Any sort of looping delay (eg. delayMicroseconds) will not work well under the circumstances. He wants a 2 mS delay, not a 15 uS delay. While delayMicroseconds was looping doing 2000 uS you could well miss the pin change on the other pin.

The jitter may not be a big problem. The timer is only used for charging, which can be off by 200 uS (see above) so the delay in loop shouldn't worry us too much.

The external interrupts have a higher priority than timer interrupts. However if we are in the middle of micros (), yes the external one(s) will be delayed a bit. The OP said "the transition to low must be as close to perfect as possible as not to effect the spark timing". Unfortunately "close to perfect" is not specified as a number. Maybe 5 uS is OK, maybe not.

I think if the "close to perfect" has to be lower than 1 uS, and without jitter, you probably need external circuitry. Judging by his requirements, if pin 2 is low, pin 5 is required to be high, and as soon as possible. Thus some sort of op-amp or other comparator could be used to achieve that. Then you just use the Arduino to do the timing delay. Or maybe the whole thing could be done with a 555 chip. :)

MarkT: If the timer0 interrupt is running (needed for micros(), millis ()) then you will get interrupt derived jitter - you might want to turn this off.

I absolutely agree that any interrupt will cause some jitter. It goes with the territory. However even a tight loop will have some jitter. This is because a loop will typically be something like:

  • Read an input
  • Compare to some value
  • Branch if condition not met
  • Do something
  • Rinse and repeat

Now depending on when the event occurs, if it is just after "read an input" then the new condition is not noticed until the compare, branch, rinse instructions are done. As an instruction cycle at 16 MHz is 62.5 nS, and many instructions take 1 or 2 cycles, then maybe your delay is 4 instructions, maybe 8 cycles, which might be 500 nS. And with two inputs to test, double that, so it might be (variable, up to) 1 uS. This is less than 3 uS to service an interrupt, but still jitter.

If you absolutely want to minimize the delay, and eliminate the jitter, I think a hardware solution is required. But hey, maybe the OP will accept 10 uS more-or-less as the response to the signal change.