How do Interrupts trigger?

I have a device (ADNS3050, optical mouse sensor) which I can attach to an interrupt pin, and this will inform me when data is available. Now I am curious as to how the main loop reacts when an Interrupt is triggered. Will the loop pause in its foot steps while the interrupt function is run? Is there a separate thread or something? I am wondering if code as follows would be OK:

int a = 0;
int b = 0;
int c = 0;
int d = 0;

void interruptFunction()
{
     a += getX1FromADNS();
     b += getX2FromADNS();
     c += getY1FromADNS();
     d += getY2FromADNS();
}
void setup()
{
     attachInterrupt(digitalPinToInterrupt(7), interruptFunction, FALLING);
}
void loop()
{
     DoLotsOfOtherStuff();
     DoSomethingWithValues(a,b,c,d);
     a = 0;
     b = 0;
     c = 0;
     d = 0;
}

Or is it possible that interruptFunction() is for example called after a and b were set to 0 but c and d had not been set yet, so I would loose the c and d values set in the interruptFunction? If this is the case, how would I go about fixing the issue (preferably without another 4 variables, as I am already pinching bytes)?

Thanks.

There are many articles written on Interrupts that cover the topic much better than I can, e.g. https://gammon.com.au/interrupts. I will say that the following is a terrible interrupt function.

void interruptFunction()
{
     a += getX1FromADNS();
     b += getX2FromADNS();
     c += getY1FromADNS();
     d += getY2FromADNS();
}

You should never call another function from within an interrupt. What you can do is set a boolean to true to cue the above function to be called from inside your main loop.

int a;
int b;
int c;
int d;
bool fooFlag;

void interruptFunction()
{
     fooFlag = true;
}

void foo()
{
     a += getX1FromADNS();
     b += getX2FromADNS();
     c += getY1FromADNS();
     d += getY2FromADNS();
     fooFlag = false;
}
void setup()
{
     attachInterrupt(digitalPinToInterrupt(7), interruptFunction, FALLING);
}
void loop()
{
     DoLotsOfOtherStuff();
     DoSomethingWithValues(a,b,c,d);
     a = 0;
     b = 0;
     c = 0;
     d = 0;
     if (fooFlag) foo();
}

You should never call another function from within an interrupt.

Nonsense. You should be aware of how long such functions could take, and be careful about whether they rely on interrupts working, but there is no reason not to call functions like micros(), digitalRead(), digitalWrite(), etc. from an ISR.

I am wondering if code as follows would be OK:

No, because the loop() function has no idea that (the uselessly named variables) a, b, c, and d can change because an interrupt occurred. So, the initial values might be stored in registers, and the registers might never be updated, because a, b, c, and d never appear to take on new values.

You need to declare variables that are used in ISRs and other functions volatile, so the values are never cached.

Perehama:
You should never call another function from within an interrupt. What you can do is set a boolean to true to cue the above function to be called from inside your main loop.

Sorry, but that's complete hogwash, you can call functions from ISR's, that's completely normal.

What you have to do is take care not to hog the processor for too long in an ISR, because other
interrupts and the main program cannot progress or react until the ISR returns and interrupts
re-enable. Some things will not work (and may hang indefinitely) from an ISR, in particular
anything that itself waits for interrupts to happen (notably delay(), Serial.xxxx())

King_bob:
Now I am curious as to how the main loop reacts when an Interrupt is triggered. Will the loop pause in its foot steps while the interrupt function is run?

Absolutely, that's the whole point. If there were any interference the program state everything would be a shambles.

Think of the interrupt as borrowing the processor for a while. It also uses the stack to do so, so there always
needs to be enough space on the stack for the ISR to run, but normally that's not a problem.

The stack is used to save the processor's complete state, so it can be completely restored on return.

PaulS:
Nonsense.

I will rephrase that to "one should never call a function from within an interrupt, unless they know what they are doing." The function attachInterrupt() actually calls the function from an ISR... My point was that we don't know what the function getX1FromADNS() does or how it's affected by interrupts, and more generally, ISRs should be as concise as possible. My other point was that one can flag a part of the procedural programming from an interrupt so that longer processes can happen in a procedural manner without interrupting for too long.

The function attachInterrupt() actually calls the function from an ISR...

The attachInterrupt function registers a callback. It does NOT call the registered function.

My point was that we don't know what the function getX1FromADNS() does or how it's affected by interrupts, and more generally, ISRs should be as concise as possible.

Then that is what you should have said. To extend that concern all the way to "never call a function from an ISR" is way too broad a generalization.

My other point was that one can flag a part of the procedural programming from an interrupt so that longer processes can happen in a procedural manner without interrupting for too long.

Whether that is necessary, or even possible, depends on what the program is doing when it is interrupted.

Suppose that the point of the ISR is to calculate RPM using a hall-effect sensor. Each time the ISR is triggered, it needs to record when it was called, so that precise RPM values can be calculated later. The ISR could call micros() OR it could set a flag indicating that it was called.

When loop() gets around to calculating RPM, based on two successive times from the ISR, it could know that there were two times, because the ISR recorded the time. Or, it could know that the ISR was called, so it now needs to record when that happened.

It's not hard to see that there can be a long time between an ISR being triggered, and setting a flag, and loop() checking that flag. Suppose the Arduino-as-a-server had just been contacted by a client.

So, the decision to set a flag or call a function is not as clear-cut as you seem to think it is.

Also, it's a good idea to use the keyword "volatile" on any variables that would change value inside an interrupt. This is a preprocessor directive to the compiler that might otherwise break the intended outcome with optimization. I am probably not describing this accurately, but I use volatile on my ISR variables.
Also, also, it's a good idea to stop interrupts while accessing those volatile variables longer than 1 byte to avoid "rollover" problems. To explain, I will try to use an allegory. say a single int is responsible for a value of time, the first byte in that int being hours and the second being minutes. We are reading this variable in a non-interrupt procedure, and we read the first byte as 3 hours, then the interrupt occurs rolling over the minutes from 59 to 00. when we return to our process, we read the second byte so the time is calculated as 3:00 not as 3:59 nor as 4:00 which either would have been more precise. So, when reading a,b,c, or d, it's a good practice to write a function that stops interrupts, reads them, then start interrupts and returns their value.

it's a good idea to stop interrupts that are longer than 1 byte while the variable is being read

You might want to reword this so that it makes sense

PaulS:
So, the decision to set a flag or call a function is not as clear-cut as you seem to think it is.

You make all good points. You are right in that it's not as clear-cut as I am making it out to be. That's why I suggested reading a bit more on the subject. I don't think it's a simple topic, but I am not ignorant to the fine lines between what is and is not possible with an ISR. I might not use the right words, but I have a pretty good handle on them. Yes, if the timing needs precision, it becomes critical to interrupt all else to obtain the precision needed. Outside of very specific needs for precision and timestamps etc. moving the rest of the calculations into the main procedural programming allows more headroom for those critical parts to execute uninterrupted.

UKHeliBob:
You might want to reword this so that it makes sense

Also, also, it's a good idea to stop interrupts while accessing those volatile variables longer than 1 byte to avoid "rollover" problems.

Also, also, it's a good idea to stop interrupts while accessing those volatile variables longer than 1 byte to avoid "rollover" problems.

It's not a rollover issue. If you are accessing a multibyte value, such as 16 bit int, it is possible that an interrupt can happen between the time that the low byte is copied and the time that the high byte is copied. If you got real lucky, the low byte was 255 and the high byte was 0, when the copy started, and the interrupt happened in between. The value that you wanted to copy was 255. What you actually copied was 255 and 1, instead of the intended 255 and 0. The resulting 16 bit register now holds 511, not 255 or 256. THAT is why you need to disable (not stop) interrupts before copying multibyte variables, and re-enable them afterwords.

PaulS:
It's not a rollover issue.

Since we are splitting hairs, specifically, WHY you want to stop interrupts while accessing a volatile variable, is because the interrupt could trigger in the middle of reading the low byte and high byte or lowest, lower, higher, and highest bytes, and the already read lower byte values would not be re read, while the higher byte values would now be read for the first time and the combination would not reflect the actual value of the variable. This could be because the value was incremented or decremented or simply reassigned etc. To summarize it as "rollover" issues is not accurate.

All this flailing of words is why there’s a specific term for this -- Atomic.

Access to larger a than 8-bit variable is non-atomic on an 8-bit AVR. On a 32-bit ARM, you can go up to 32 bits and still be atomic.

Since boards in the Arduino ecosystem are (mostly?) single-core, single-thread, single-whatever, atomicity comes into play mostly with variables shared between ISR and regular code.

Perehama:
Also, it's a good idea to use the keyword "volatile" on any variables that would change value inside an interrupt. This is a preprocessor directive to the compiler that might otherwise break the intended outcome with optimization. I am probably not describing this accurately, but I use volatile on my ISR variables.
Also, also, it's a good idea to stop interrupts while accessing those volatile variables longer than 1 byte to avoid "rollover" problems. To explain, I will try to use an allegory. say a single int is responsible for a value of time, the first byte in that int being hours and the second being minutes. We are reading this variable in a non-interrupt procedure, and we read the first byte as 3 hours, then the interrupt occurs rolling over the minutes from 59 to 00. when we return to our process, we read the second byte so the time is calculated as 3:00 not as 3:59 nor as 4:00 which either would have been more precise. So, when reading a,b,c, or d, it's a good practice to write a function that stops interrupts, reads them, then start interrupts and returns their value.

The proper term for this is actually "race condition". Two threads (the main loop and the interrupt) are attempting to modify the value at the same time, and the end result depends on exactly when the interrupt inserted itself into the process.

Interrupts are not quite the same as multi-threading, but they're similar enough in concept that they share a lot of the same problems.

Jiggy-Ninja:
The proper term for this is actually "race condition". Two threads (the main loop and the interrupt) are attempting to modify the value at the same time, and the end result depends on exactly when the interrupt inserted itself into the process.

Only ONE of the code sequences (they are NOT "threads") needs to be modifying the value to create the hazard...
Regards,
Ray L.

RayLivingston:
Only ONE of the code sequences (they are NOT "threads") needs to be modifying the value to create the hazard...
Regards,
Ray L.

You are correct. The minimum situation required to create this problem is for one "thread" to be accessing the variable at the same time another is modifying it.

I know that interrupts are not exactly the same as threads. I say so explicitly in the second paragraph that you cut out of the quotation. But they are similar enough to have some of the same problems with similar solutions. Race conditions like this require atomic operations to solve. Mutexes, and locks are used to solve this in multi-threading, while for an interrupt system you just need to disable interrupts during the critical section.

Jiggy-Ninja:
I know that interrupts are not exactly the same as threads. I say so explicitly in the second paragraph that you cut out of the quotation. But they are similar enough to have some of the same problems with similar solutions.

Yes, but that fine distinction will be lost on novice users, so using the term "thread", even with a disclaimer, will be mis-leading and confusing. There are far more differences than similarities, and newbies will be familiar with exactly zero of either. There is hardly a day goes by on this forum that there is not some newbie making a post talking about "threads"....
Regards,
Ray L.

King_bob:
Will the loop pause in its foot steps while the interrupt function is run? Is there a separate thread or something?

[......]

Or is it possible that interruptFunction() is for example called after a and b were set to 0 but c and d had not been set yet, so I would loose the c and d values set in the interruptFunction? If this is the case, how would I go about fixing the issue (preferably without another 4 variables, as I am already pinching bytes)?

Now that we have had all the theory let's see about an answer to the question ... :slight_smile:

When an interrupt is triggered the code that is being implemented stops where it is and implementation switches to the code in the ISR. When the ISR completes the code takes up where it left off as though there had been no interruption. It is important to ensure that the time spent in an ISR is as short as possible - 100µsecs would be a long time.

There is no means to predict when an interrupt will happen (if you could predict it then you not need an interrupt) so, yes, i could happen in the middle of a sequence of lines of code that reset the values of variables. If it is essential to perform a few actions together without them being interrupted then you can pause interrupts like this

noInterrupts():
     a = 0;
     b = 0;
     c = 0;
     d = 0;
interrupts();

It is often useful to make working copies of variables that get updated in an ISR to ensure that the same values can be used in a succession of calculations and that can be done in the same way

noInterrupts():
     copyA = a;
     copyB = b;
     copyC = c;
     copyD = d;
interrupts();

If the interrupts do not happen too frequently and if the data from the sensor does not change between interrupts then a simpler way can be to use the ISR to set a flag variable to tell the main program that an interrupt has happened. Then the main program can get the data from the sensor and reset the flag when it has done so - something like

void interruptFunction() {
   newData = true;
}

and in loop()

   if (newData == true) {
      // code to get data from sensor
      newData = false;
   }

...R

PS ... there is nothing in principle wrong with calling a function from an ISR. However it would be A VERY BAD IDEA to call a function from an ISR that could also be called from the main code (unless you are a very clever programmer) because the main code may have called the function when the interrupt happens - like two people trying to drink from the same glass of beer without spilling it.

Since noInterrupts() seems to delay an interrupt that happens during this time, it seems that this would be the ideal solution (in combination with the volatile keyword)

Now the next question is, can an interrupt trigger during an interrupt?