Uneven respone to interrupt command

Interrupt latency is dependant on many things, not the least of which is the fact that another interrupt may be being handled when your trigger arrives.

I don't know that you can do better than that on an Arduino, I haven't looked at your code yet, can you disable the timer interrupt being used for millis()?

If you need a really fast response polling is faster.


Rob

The Arduino has usually the timer interrupt for timer 0 active. While this interrupt is processed your interrupt will be delayed. This is what you see with your scope. So what you need to do either stop these interrupts or change the libraries to allow interrupts while the timer interrupt is active. Both approaches have some sideeffects though.

In my code I am using the Interrupt0. Do you think it helps using another Interrupt port?

It doesn't matter which interrupt you use, the problem is the same.

Do you need to do anything else while waiting for this trigger?


Rob

Yes I do something else between the interrupts (including short analog read and several other calculations, LCD submission...). Pls see my code under "external()".

Yes I see.

Couple of things,

void ignite()
{  
  mi=micros(); 
  digitalWrite(LEDpin, HIGH);//für LED
  microshigh2 = micros();
  deltamicros= microshigh2-microshigh1;
  digitalWrite(LEDpin, LOW);//für LED
  zzpcalc(); //Starte Berechnung des ZZP sofort nach Zündung
}

Don't use digitalWrite(), you can save a couple of uS by directly writing to the port.

Don't use attachInterrupt(), you can save a few more uS by writing your own ISR because the default handler does a test to see if a function has been attached.

So your code may look like this (totally untested)...

...
float volatile deltamicros;  // note volatile added, BTW why is this a float?
boolean volatile do_calc = false;
...

ISR (INT0_vect) {
  mi = micros(); 
  PORTB != (1 << PB5);
  microshigh2 = micros();
  deltamicros = microshigh2-microshigh1;
  PORTB &= ~(1 << PB5);
  
   // never call an large function from an ISR, set a flag and call it from loop()
  do_calc = true; 
}


void setup() {
   ...
}

void loop() {


  if (do_calc) {
    zzpcalc(); 
    do_calc = false;
  }

  external();

}

You are using micros() which IIRC in turn needs timer0 to be running and that will cause some jitter for the reasons we stated above.
It may also be worth disabling the timer0 interrupt and getting your micros information directly from the timer hardware. But I haven't delved deeply into your logic so understand what's needed.

NOTE: Why is deltamicros a float, expecting fractional values?


Rob

What exactly is this coding going to accomplish? IMHO 4 microseconds = 64 cycles is plenty of time. However it would help a lot to understand what you are doing and what your constraints actually are. According to the comments it looks like some kind of combustion engine control. But then again I could be wrong.

With regard to the timing: depending on how far you are going to push it even 1 microseconds = 16 cycles latency is not to hard to achieve. For some very specific project I once implemented something with a guaranteed latency of 8 cycles. Unless it is clear what you really need it stays unclear if you need techniques similar to what I did or if something much simpler might suffice (simpler == much easier to debug).

Hi Udo,
I like your approach (without dislike of the other helpful forum member!).
What I try to achieve is to get a 5V trigger signal from outside and kick a 5V signal out after a certain time. I think from my code using functions like "ignite" its clear that this will be an engine ignition. I receive a trigger from Flywheel and kick the sinusoidal 5V signal (rectangularized by a comparator) out with a certain delay. I will use the "while" command to execute a delay without using "delay()" command.
The tests I have done shown on the osicilloscope where done without any delay function but only immediate putting the PIN to HIGH only.

The only thing I need is a consistent execution of ISR...not necessarily a quick one. I need to trust this execution time is correct without jumping around 20microseconds

kick the sinusoidal 5V signal (rectangularized by a comparator) out

This I don't understand, where does the sine wave come from? The Arduino can't produce one and even if it could if you are squaring it anyway there's no point.

with a certain delay.

Can this delay be calculated before you need the pulse? IE from the previous cycle.

If so you can poll the input trigger then do your own while loop to get the delay and another to time the pulse. Then when that's done do all the calculations for the next cycle.

This will have just a uS or two of latency and jitter.

And I repeat, why does anything to do with micros need a float variable.


Rob

Graynomad,
sinusoidal signal is coming from a inductive trigger "looking" to the flywheel

I calculated this delay AFTER the ignite to reduce any unforeseeable time delay.

I calculated this delay AFTER the ignite

Then the processor has nothing better to do so poll the trigger input (much faster than interrupts) and do two loops to time the delay and pulse width.


Rob

@soulid: I understand your point that you want consistent responses. However you will always pick up some jitter, for example while digitizing the analog signal. What I am aiming at: what is the maximum RPM of you engine and what angular resolution do you really need.

Example: suppose max RPM = 12000 RPM and angular resolution ~ 1°. Then resolution in clock cycles = 16 000 000 * 60 / (12 000 * 360) = 222 ticks (or 13 microseconds). --> You better be precise to ~5 microseconds.

With regard to consistent responses. Turn of all interrupts the Arduino provides by default. Then switch on only what you really need. Actually as it was already proposed you can do this without any interrupts at all.

However since you need to do some other stuff at the same time this might not be an option. I can think of several solutions:

  1. Disable all other interrupts but the interrupt for the signal you want to delay. Process the delay in your interrupt routine and then go back to the main program. It follows that you main program must not rely on any timing functions and must not lock interrupts for more than say << 10 microseconds

  2. Same as above but instead of a delay enable an additional interrupt in the timer code and set up a timer interrupt for the ouput.

  3. Same as (1) but instead of generating the output in software use the PWM hardware to generate just this single pulse. Take care to disable the PWM early enough.

There are some mandatory actions here:
a) Read the datasheet on interrupts, especially timer interrupts.
b) Analyze the Arduino interrupt setup code (not to much and not to hard to understand). Do not forget the interrupts caused by communications
c) Analyze the LCD libraries if it has anything timing critical that might block interrupts.

However you will always pick up some jitter,

He could always use an LPC ARM, they can be set to have jitter-free interrupts :slight_smile:

At 12000RPM he has 5mS between pulses and the pulses don't look to be very long so most of that time is spent waiting for the next pulse or, writing to an LCD or calculating a pulse width.

LCDs are slow but I think if the data for it is put in a ring buffer and for example one byte sent every in inter-pulse period that shouldn't get in the way.

I'm thinking pseudo code like this

loop {
wait for trigger
do pulse
calc value
write stuff to LCD ring buffer if necessary
read a byte from ring buffer and send to LCD
}

No interrupts required (and disable any default interrupts) and "wait for trigger" is a tight polling loop.


Rob

That does not work for me. Think about:

  1. Analog read with a prescaler set to 16: 104 microseconds (and I have two of them)
  2. LCD write with 4bit: takes more than 20 microseconds as well

Both single functoins are far to slow to "wait" for the trigger

What I did was the following

Pseudocode


setup
attachInterrupt

loop
{
do-other-things
}

function Interrupt action
put PIN13->high
jump to calculate_only_once_after_interrupt


function calculate_only_once_after_interrupt
calculatecalculatecalculatecalculatecalculate


function do-other-things
x=x+1
if x==8->x=1

if x=1
If the last LCD write was more than 25mS away-> write LCD
if x=2
read analog1

if x=2
read analog2

.
.
.

if x=8
write Pinxyz HIGH

The function do-other-things will lead to do one action out of 8 when called. That ensures proper execution of each function.

  1. Analog read with a prescaler set to 16: 104 microseconds (and I have two of them)

The Arduino implementation of Analog Read is blocking.

int analogRead(uint8_t pin)
{
	uint8_t low, high;

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif
	
#if defined(__AVR_ATmega32U4__)
	pin = analogPinToChannel(pin);
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#elif defined(ADCSRB) && defined(MUX5)
	// the MUX5 bit of ADCSRB selects whether we're reading from channels
	// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  
	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
	// to 0 (the default).
#if defined(ADMUX)
	ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif

	// without a delay, we seem to read from the wrong channel
	//delay(1);

#if defined(ADCSRA) && defined(ADCL)
	// start the conversion
	sbi(ADCSRA, ADSC);

	// ADSC is cleared when the conversion finishes
	while (bit_is_set(ADCSRA, ADSC));

	// we have to read ADCL first; doing so locks both ADCL
	// and ADCH until ADCH is read.  reading ADCL second would
	// cause the results of each conversion to be discarded,
	// as ADCL and ADCH would be locked when it completed.
	low  = ADCL;
	high = ADCH;
#else
	// we dont have an ADC, return 0
	low  = 0;
	high = 0;
#endif

	// combine the two bytes
	return (high << 8) | low;
}

The issue is this:

	// ADSC is cleared when the conversion finishes
	while (bit_is_set(ADCSRA, ADSC));

In your situation I would have rolled my own non blocking Analog Read funtion. Of course this implies that the whole code is implemented "state engine style".

  1. Analog read with a prescaler set to 16: 104 microseconds (and I have two of them)
  2. LCD write with 4bit: takes more than 20 microseconds as well

Both single functions are far to slow to "wait" for the trigger

You do them after the pulse, as far as I can see you have nearly 5mS, you could spell check War and Peace in that time :slight_smile:

Your psuedo code still uses attachInterrupt() right away you are behind the 8 ball with regard to latency and jitter. It never puts pin13 low and "jumps" to calculate_only_once_after_interrupt which you really can't (or shouldn't) do in C.

Am I correct in saying that the width of the pulse doesn't matter? That seems to be the case because you invoke that huge calculate function from inside the ISR.

I think you are doing the same as I did but still using interrupts and it's the interrupts that are the root of the problem.


Rob

Graynomad:
Yes I see.

//THIS interrupt should be disabled inside the ISR, to prevent multiple calls to the ISR. You should not use millis() & micros() from an ISR

...

ISR (INT0_vect) {

mi = micros();
 PORTB != (1 << PB5);
 microshigh2 = micros();
 deltamicros = microshigh2-microshigh1;
 PORTB &= ~(1 << PB5);
 
  // never call an large function from an ISR, set a flag and call it from loop()
 do_calc = true;
}

void setup() {
  ...
}

void loop() {

if (do_calc) {
   zzpcalc();
   do_calc = false;
 }

external();

}




You are using micros() which IIRC in turn needs timer0 to be running and that will cause some jitter for the reasons we stated above.
It may also be worth disabling the timer0 interrupt and getting your micros information directly from the timer hardware. But I haven't delved deeply into your logic so understand what's needed.

NOTE: Why is deltamicros a float, expecting fractional values?

______
Rob

If you want the best repeatability you need to turn off all other interrupts. Times and Serial all use interrupts and can affect response time to an incoming interrupt. You also have to consider what instructions might be executing. What is the longest time for any instruction a- that will make for your worst case response time. If another interrupt is executing - that will be your longest response time.

Dear all,
the essence I am reading is (and please correct me if I am wrong):

  • Change the analoge reading method (posted by Udo Klein). Sounds feasible although I need to regret that I do not have ANY clue regarding lines like "ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);". I need to read and learn!

  • Do not use any other interrupts beside the "ignite" one. No micros(), no millis(). Although this might be correct, I do not know how to establish a variable time delay on microseconds base to change ignition retard... :~