Clarifications about interrupt routines

I had a few questions about ISRs that i hoped you could help me to answer, I've used them before but never needed to study their methods of operation in great detail so there are some specific aspects about them, which don't seem that clear in the arduino documentation webpages, and which I've never quite understood but suddenly need to:

1.What happens if another interrupt fires when an ISR is in progress?
I understand that functions called by interrupts will not themselves ever be interrupted but the page at attachInterrupt() - Arduino Reference says "If your sketch uses multiple ISRs, only one can run at a time, other interrupts will be executed after the current one finishes in an order that depends on the priority they have". And I'm not sure how to interpret it. Right now I'm in a situation where I need to use an interrupt routine to detect a pin falling to low and then inside the function that gets called by the interrupt I have quite a few instances of delayMicroseconds() because I am reading a digital signal which is coming in on that pin. Now if after this interrupt function completes I get the same function triggered again and again for every falling edge that may have happened during the message that the function was picking up I could have some severe problems. What must I do to ensure that if during the interrupt any conditions arise which would trigegr the same interrupt again then that re-triggered interrupt will NOT run at all and will not run after the current interrupt finishes? I would be happy with any method which guarantees that any interrupts triggered during another interrupt will not run at all.

2.I know about the need for volatile variables if you have variables which get their values changed by an interrupt ad then need to be read or modified in the main loop of the code. I also know that in the main loop you typically need an

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
  code... 
}

to contain lines where those volatile variables get copied into non-volatile ones, so as to ensure that the interrupt can't occur while the copying is in progress. But what about the other way around? Is there any need to have volatile variables if they are being set in the main code and read in the interrupt? What about the use of atomic blocks in these circumstances, if a variable which gets modified in the main loop of code needs to be read in an interrupt then should any places in the main code where that variable gets altered be surrounded by an atomic block, hence ensuring that an interrupt which reads from this variable can't trigger at a time when the variable's value is in the middle of being changed?

3.Can a piece of code have multiple interrupts present, I never want them to run at the same time but I've got a need for one interrupt to do free running conversions on an ADC, another for receiving I2C messages from a master device (the arduino is a slave) and replying in an onRequest callback and yet another to monitor digital pin 2? Is it ok o have multiple interrupts present, on the whole these interrupts don't share any variables between them, each just shares things with the main loop of code.

4.If your main script has two separate atomic blocks in it as follows

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
  code... 
}
//gap between them
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
  other code... 
}

can an interrupt occur in that "gap between them", I recognise that in that gap no operations is being performed so I guess that there are either zero or very few clock cycles between the two atomic blocks, but I'm unsure as to whether (if there are a non-zero numebr of clock cycles to end one atomic block and start another) an interrupt could be triggered in that gap? if you wanted to ensure that interrupts could trigger in the gap would you be better doing some junk calculations during that gap to occupy a few clock cycles during which an interrupt may trigger?

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
  code... 
}
int answer=(54+80+100)/2;
int answer2=6;
int answer3=answer+answer2;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
  other code... 
}

??

5.Is it ever necessary to put atomic blocks into something like

byte number =65;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
byte answer=(number+75)/2
}

or does the use of bytes mean that only takes one clock cycle anyway? What if ints of floats or longs were in use? Also is there any practical limit to how large a number of lines can be in an atomic block, or can it be any length so long as you are ok with not having interrupts occur during the block?

6.Do atomic blocks prevent things like delay() from working within them? I understand that delay() involves an interrupt as does the incrementation of millis(). Also can other functions be called from within an atomic block?

7.Does the arduino pulseIn() function require interrupts to be used? The documentation says that pulseInLong() does but doesn't state whether pulseIn does or not? Can pulseIn run within interrupt routines and/or atomic blocks?

8.Lastly, and similar to my first question in some ways, if an interrupt occurs while an atomic block is running then will the interrupt code run after the atomic bock has finished, or not at all?

Thanks

There's a great tutorial on interrupts here by Nick Gammon
Have a look at that first and then we can address questions that that hasn't answered.

Thanks and great appreciation for the inquisitiveness you have shown for understanding the Interrupt Process of the ATmega328P Microcontroller. Let us make an attempt to begin study of the interrupt process from low level platform taking the MCU as a starting point, and then we slowly move to complicated situations, and then we move to the high level platform of Arduino. Your post will help us to learn the interrupt process in much better way by virtue of the participation of the veterans who for most of the times write in popular styles.

Once the fundamental principles of the Interrupt Process are clear to us, we can easily bring new question/complication into it which can be solved/answered by consulting data sheets and performing experiments on the Arduino UNO. Let us remember that playing with things that work is the best way of self learning.

A: Meaning of Interrupting the ATmega328P Microcontroller
Interrupting the ATmega328P Microcontroller (MCU) means:


Figure-1: Diagram to explain the meaning of interrupting the MCU

(1) Telling the MCU to suspend (not stop) what it has been doing (the main line program, MLP). Example of an MLP: The MCU has been continuously blinking L (the built-in LED of UNO) Fig-1.

(2) When an Interrupt Request Signal (IRQ) is placed on 'Physical Pin-4 (Pin-4)' of the MCU via 'Digital Pin-2 (DPin-2)', the following events occur:
(a) The MCU finishes the current instruction what it has been doing at the time of the arrival of the IRQ-signal.

(b) The MCU saves the 'Return Address (the label of the program from which the MLP will be resumed after finishing the Interrupt Service Routine) on a special block of memory known as stack.

(c) The MCU disables all interrupt logic (known as global interrupt enable/disable bit/flag) to prevent the MCU from being interrupted by another IRQ-signal (say, at DPin-3 of Fig-1) until the current Interrupt Service Routine (ISR) is finished.

Remember: the MCU does not disable the interrupt logic (called local interrupt enable/disable bit/flag) that belongs to Interrupt Process of DPin-2 (known as interrupt type 0 -- INT0).

(d) The MCU is forced to go to the side job known as 'Interrupt Service Routine due to IRQ-signal at DPin-2 (ISRINT0)'. Let us assume that the job of the ISRINT0 is to blink LED2 only for 5 times at 1-sec interval.

(e) The MCU finishes the ISR, and it executes the RETI (Return to MLP from ISR) instruction. During the execution of the RETI instruction, the Global Interrupt Flag is automatically enabled. Interestingly, the MCU is not interrupted during the execution of RETI instruction; but, it is interrupted during the execution of all other instructions.

(f) The MCU resumes the MLP from the label which has been saved in Step-b above. The MCU is ready to accept and honor the next IRQ-signal.

B: Converting all the Steps of Section-A into a working Program
(1) The PD2 line of Port-D will now work as an 'Interrupt Line'.

(2) The interrupting device is K1 (let us forget the bouncing of K1). It puts Falling Edge signal (the IRQ-signal) on DPin-2 when pressed down and released. That means the trigger level of the IRQ-signal is Falling Edge. We need to add a an external pull-up resistor (Rexp = 2.2k) with Pin-4 (PD2/DPin-2).

(3) The name of the ISR is ISRINT0.

(4) The local interrupt flag bit for INT0 interrupt must be enabled.

All the above 4 tasks are implemented when we execute the following instruction:

attachInterrupt(digitalPinToInterrupt(2), ISRINT0, FALLING);

(4) By default, the 'Global Interrupt Flag' is at disabled state; let us enable it by executing one of the following two instructions:

interrupts();
sei();

C: The sketch:

void setup()
{
  pinMode(13, OUTPUT);
  pinMode(8, OUTPUT);
  digitalWrite(13, LOW);
  digitalWrite(8, LOW);

  //----1-sec time delay parameters; we will not use delay() function----
  TCCR1A = 0x00;
  TCCR1B = 0x00;
  TCNT1 = 0xC2F7;  //preload 1-sec timeDelay count
  TCCR1B = 0x05;   //Timer-1 is ON
  //---------------------
  attachInterrupt(digitalPinToInterrupt(2), ISRINT0, FALLING);
  interrupts();
}

void loop()
{
  digitalWrite(13, !digitalRead(13));
  timeDelay();  //we are not using Arduino's delay() function
}

void ISRINT0()
{
  int counter = 0x05;
  do
  {
    digitalWrite(8, !digitalRead(8));
    timeDelay();
    counter--;
  }
  while (counter != 0);
}

void timeDelay()  //genertaing 1-sec tine delay using TC-1 of MCU
{
  while (bitRead(TIFR1, 0) != HIGH)   //wait until 1-sec is elapsed
  {
    ;
  }
  bitSet(TIFR1, 0);    //Timer-1 overflow flag is cleared
  TCNT1 = 0xC2F7;   //reload preset value
}

(1) Build the circuit of Fig-1.

(2) Upload the above sketch.

(3) Check that L is blinking at 1-sec interval.

(4 Gently and quickly just press/release K1.

(5) Check that L is not blinking.

(6) Check that L has stopped blinking; LED2 has started blinking at 1-sec interval.

(7) Check that L has stopped blinking after 5 blinks.

(8) Check that L has started blinking again.

D: Add Complication
(1) In the skecth of Section-C, insert this instruction delay(1000); in place of timeDelay() both in the MP and ISRINT0.

(2) Executes Step-1 to 8 of Section-C.

(3) Record your observation and slowly analyse them having have a good understanding how the Arduino's delay(1000); function works.

BTW: The delay() function works well in MLP; but, it does not work in ISRINT0.

(4) Insert this instruction interrupts(); just after arriving at the ISRINT0. Executes Step-1 to 8 of Section-C. Record your observation and slowly analyse the result.

Now, the delay() function works in the ISRINT0.

Hope, this startup tutorial will help you to find answers of your interrupt related queries.

Very long initial post, but which platform? I suppose the AVR ATmega is the platform as first and main for Arduino.

  1. At the beginning of ISR the interrupts are disabled by default and enabled back when it finish so no other ISR can start execution. The ATmega has internal register with flags for each interrupt. It ensures to remember one event from each interrupt and any other will be missed. Execution order is according their priority.
  2. Atomic block is just a macro which disables the interrupt at the beginning and enable the interrupts at the end. Volatile variable ensure that it will be retrieved from the memory (not keeping value in registers) at each manipulation to ensure that actual value is taken, and yes, it can be changed exactly at the moment after read if there is no atomic block.
  3. Yes, you can have multiple interrupts present.
  4. Yes, the ISR can start to be executed in the gap. The interrupts can be re-enabled by two ways. The first ensure that at least one instruction from main code have to be executed after IST prior to start another ISR. The second method enable to start another ISR immediately.
  5. Division is on 8bit platform pretty long function with many instruction in order. Also + operation can be more than 1 instruction and usually it is, so the interrupt can occur during this operation.
  6. The delay will never finish because the interrupts are disabled and timer ISR cannot increment the value on which the delay loop depends.

Infraviolet:
1.What happens if another interrupt fires when an ISR is in progress?

1. There are 26 sources of various interrupt signals in the ATmega328P architecture. Each interrupt has its own vector address. An interrupt with lower vector address has the higher priority than the one with higher vector address. Thus, INT0 with vector address 0x0002 has the higher priority than INT1 interrupt with vector address 0x0004. Interrupt with higher priority is processed first. There is no option for 'priority rotation' in the ATmega328P architecture, which we can find in the 8259 Interrupt Priority Controller of 80x86 Microprocessor Family.

2. Assume that ISRINT0 is in progress due to INT0 interrupt. What will happen if INT1 occurs before ISRINT0 is finished?

INT1 has the lower priority than INT0; so, ISRINT0 will not be interrupted (suspended). The MCU will remember the occurence of INT1 interrupt. The MCU will finish ISRINT0, and it will go back to MLP. The MCU will execute only one instruction in the MLP, and then it will jump to ISRINT1.

Note: When INT0 occured, the MCU disabled the global interrupt flag. After arriving at the ISRINT0, we may enable it; but, the situation will not be different than what has been described above; it is due to the reason that INT1 has the lower priority than INT0.

3. Assume that ISRINT1 is in progress due to INT1 interrupt. What will happen if INT0 occurs before ISRINT1 is finished? Assume that interrupt logic has been enabled after arriving at the ISRINT1 by executing the interrupts() instruction.

INT0 has the higher priority than INT1; so, ISRINT1 will be interrupted (suspended). The MCU will suspend the current ISRINT1 and will jump to the ISRINT0. After the completion of ISRINT0, the MCU will return back to ISRINT1; it will finish the remaining codes of ISRINT1, and then the MCU will return to MLP.

Note: When INT1 occured, the MCU disabled the global interrupt flag. If interrupt is not enabled after arriving at the ISRINT1, the MCU will not divert to INSRINT0 routine due to INT0 interrupt though it has higher priority that INT1. The MCU will remember the occurence of INT0 interrupt. The MCU will finish ISRINT1, and it will go back to MLP. The MCU will execute only one instruction in the MLP, and then it will jump to ISRINT0.

The above propositions could be demonstrated using the following setup and the associated codes.

void setup()
{
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);   //MLP
  pinMode(8, OUTPUT);       //ISTINT0
  digitalWrite(8, LOW);

  pinMode(9, OUTPUT);       //ISTINT1
  digitalWrite(9, LOW);


  //--------------------
  TCCR1A = 0x00;
  TCCR1B = 0x00;
  TCNT1 = 0xC2F7;  //1-sec timeDelay count
  TCCR1B = 0x05;   //Timer-1 is ON with internal clkTC1 = clkSYS/1024 = 16000000/1024 = 15625 Hz
  //---------------------
  attachInterrupt(digitalPinToInterrupt(2), ISRINT0, FALLING);
  attachInterrupt(digitalPinToInterrupt(3), ISRINT1, FALLING);

  interrupts();
}

void loop()
{
  digitalWrite(13, !digitalRead(13));
  timeDelay();  //we are not using Arduino's delay() function
}

void ISRINT0()
{
  int counter = 0x05;
  do
  {
    digitalWrite(8, !digitalRead(8));
    timeDelay();
    counter--;
  }
  while (counter != 0);
}

void ISRINT1()
{
  int counter = 0x05;
  do
  {
    digitalWrite(9, !digitalRead(9));
    timeDelay();
    counter--;
  }
  while (counter != 0);
}

void timeDelay()  //genertaing 1-sec tine delay using TC-1 of MCU
{
  while (bitRead(TIFR1, 0) != HIGH)
  {
    ;
  }
  bitSet(TIFR1, 0);  //clear Timer-1 overflow flag
  TCNT1 = 0xC2F7;    //reload value for 1-sec time delay.
}

Budvar10:

  1. At the beginning of ISR the interrupts are disabled by default and enabled back when it finish so no other ISR can start execution.

I understand that the interrupt is disabled by default; it is enabled by the user program in the setup() function so that the MCU can respond to an interrupt.

The MCU disables the interrupt logic (the global interrupt bit, and not the local interrupt bit) before going to the ISR. So at the beginning of the ISR, the interrupt is at disabled state not by default -- but knowingly so that the the current ISR is not interrupted. However, the user must enable the interrupt after arriving at the ISR if he wants his current ISR is to be interrupted by a higher priority interrupt.

DKWatson:
There's a great tutorial on interrupts here by Nick Gammon
Have a look at that first and then we can address questions that that hasn't answered.

I think that is good advice because the Original Post shows a general misunderstanding of how interrupts should be used.

For example there should never be a delay() or delayMicroseconds() inside an ISR. And if the code is changed to eliminate that then many of the other questions will probably go away.

...R

In regards to post #2, I went and ran some tests based on your proposals, they were quite helpful in informing me of how the arduino behaved by default. I found that including EIFR=1; at the end of the ISR function ensured that if a flag for the interrupot were set while the interrupt was running then such a flag would be cleared before the interrupt ended and the interrupt would not run twice in a row.

Post #6, if one should NEVER use any forms of delayMicroseconds() inside an ISR (obviously one should never use normal delay() as that won't work at all without other interrupts active) how else is one to measure the values of a digital signal as it arrives, unless one delays between each sampling time? If 01101000 is being fed in and each bit with value one is respresented by a long pulse followed by a short gap and each bit with value zero is represented by a short pulse followed by a short gap then how is one to avoid delays between reading the pin value? I've had things like

uint16_t MyTimer=0;
uint16_t MyTimer2=0;
byte CountAByte=0;

byte MyDataInArray[8]={0,0,0,0,0,0,0,0};
while (CountAByte <8 && MyTimer2<1300){
 MyTimer=0;
 MyTimer2=0;

 while(((PIND & B00000100)) && (MyTimer2 < 1300)){//while pin is high
   delayMicroseconds(1);
   MyTimer2=MyTimer2+1;
 }
 if(MyTimer<350){//pin was low for a short period (0)
   MyDataInArray[CountAByte]=0;
 }else if(IRTimer<900){//pin was low a long time (1)
   MyDataInArray[CountAByte]=1;
 }else{
  //dodgy pulse detected
  CountAByte=9;
 }
 CountAByte=CountAByte+1;
 while ((!(PIND & B00000100)) && (MyTimer < 1300)){ //this reads as "while (not (PIND anded with B00000100))" and should evaluate as true when arduino pin 2 is low, the && timer < 1300 ensures we leave this while if it goes on for far too long
  delayMicroseconds(1);
  MyTimer=MyTimer+1;//times how long it is until pin rises
}
}
//along with some other stuff, not included here, this gets looepd round eight times to capture each byte of the incoming data

within an interrupt to check how long (by reading the value of MyTimer2 once that loop is complete) the incoming pulses are. The 1300 is there to ensure that the loop finishes even if some kind of error occurs which makes a pulse last far longer than the 800 or so microseconds it should last. Without such things how can one measure incoming signals which arrive at unpredictable times?

Thanks

millis() and family are functions of convenience. Read and try to understand the datasheet (I know it's tough). The timers/counters continue to do their thing at the hardware level (unless YOU turn them off) independent of any instructions being executed. What gets disrupted by interrupts is that timer0 uses the overflow interrupt to count the number of times it ramps through the count. When you move up a pay-grade where this impacts your timing, it's time to put away the toys and get your hands dirty. That's what timer1 is there for. You store the value of TCNT1 at the beginning and end of you timing sequence, thereby recording the number of clock pulses elapsed. Given your knowledge of the pre-scaler you can then calculate the time. You choose the resolution of the timer. I use one configuration raw to determine the number of clock pulses used by each C/C++ instruction and then decide if in-line assembly of my own would be quicker (not always, just if I need to speed things up). You open up a whole new world of possibilities, at a price. You need to shed the convenience of somebody else's code and write your own.