8
\$\begingroup\$

(Specific to the Arduino Uno...)

What happens to the stack when an interrupt occurs on an AVR microcontroller and I call a function? Does the compiler inline the code? Does it cache the stack somewhere and then reset the stack pointer? Does it have a secondary stack for interrupts only?

As I understand, the vector for the interrupt is a direct GOTO command in assembly. Also, I don't think that the microcontroller would automatically mess with the stack, so it's probably left alone. However, that still doesn't explain how functions work during an ISR.

\$\endgroup\$
6
  • \$\begingroup\$ The answer below is great, just please note that the compiler can not inline interrupt calls because... They're interrupt calls :D \$\endgroup\$ Jan 17, 2015 at 13:44
  • 1
    \$\begingroup\$ @VladimirCravero I meant inline the functions called within the interrupt (if I call foo() in the ISR does it inline it so it's not really a function call?) \$\endgroup\$ Jan 17, 2015 at 13:51
  • \$\begingroup\$ So the answer below does not answer your question, or does it? caveman explains what happens when an interrupt occurs, but not what happens when a function is called in an interrupted context. the answer to the latter would be: nothing special, the function gets called and that's it. the magic happens when the interrupt is triggered, what happens after (before the iret) is just normal, usually uninterruptible, code. \$\endgroup\$ Jan 17, 2015 at 13:54
  • \$\begingroup\$ @VladimirCravero yes it does (indirectly). I was talking about how the stack was modified for an ISR, thinking that it had to be modified to use functions in the first place. I'm going to guess that functions work the same exact way after the ISR is setup. \$\endgroup\$ Jan 17, 2015 at 14:03
  • \$\begingroup\$ well, you got it then. after the jump to the interrupt vector everything is fine and you can goto around wherever you want. \$\endgroup\$ Jan 17, 2015 at 14:05

1 Answer 1

16
\$\begingroup\$

The AVR is a RISC architecture, so it has pretty basic hardware handling of interrupts. Most processors mess with the stack during interrupts, though there are a couple, most notably ARM and PowerPC, that use different methods.

In any case, this is what the AVR does for interrupts:

When an interrupt occurs, the processor hardware does these steps, which are not just a simple GOTO:

  1. Finish the current instruction.
  2. Disable the global interrupt flag.
  3. Push the address of the next instruction on the stack.
  4. Copy the address in the correct interrupt vector (according to the interrupt that occurred) into the program counter.

Now at this point, the hardware has done all it is going to do. The software has to be written correctly to not break things. Typically, the next steps are along these lines.

  1. Push the status register onto the stack. (This must be done first before it is changed).

  2. Push any CPU registers that will (or may be) changed onto the stack. Which registers that need to be saved in this way are defined by the programming model. The programming model is defined by the compiler.

Now the working interrupt code can be run. To answer the case in the question of calling a function, it just does what it always does, push the return value on the stack, then pop it back when done. This doesn't affect any of these previous values that we saved on the stack until now.

  1. Run ISR working code.

Now we are done and want to return from interrupt. First we have to do software cleanup.

  1. Pop the CPU registers we pushed in step 6.
  2. Pop the saved status value back into the status register. After this we have to be careful not to execute any instruction that could change the status register.
  3. Execute the RTI instruction. The hardware does these steps for this instruction:

    a. Enable the global interrupt flag. (Note that at least one instruction must be run before the next interrupt will be honored. This prevents heavy interrupts from fully blocking background work.)

    b. Pop the saved return address into the PC.

Now we are back to normal code.

Note that there are some points where we have to be very careful, particularly around the status register and saving registers that could be changed. Luckily if you are using a C compiler, all of this is handled under the covers.

Also, you have to watch your stack depth. At any point when interrupts are enabled, an ISR could use more of the stack than is obvious by looking at local code. Of course, this really doesn't come up much unless you are pushing your memory to its limits.

Here is a link describing this process if you want a reference.

\$\endgroup\$
6
  • \$\begingroup\$ What's the purpose of steps 5/6? It seems silly to me to not directly modify the registers, although I guess messing with some registers during interrupts could create some nasty results. \$\endgroup\$ Jan 17, 2015 at 5:33
  • 1
    \$\begingroup\$ Steps 5 and 6 save the current (before interrupt) state of the system, so it can be restored (steps 8 and 9) after the interrupt completes, to allow the main program to continue as if it was not interrupted. \$\endgroup\$ Jan 17, 2015 at 5:49
  • 1
    \$\begingroup\$ This is an excellent summary. \$\endgroup\$ Jan 17, 2015 at 6:10
  • 3
    \$\begingroup\$ It's also worth noting, that if you really, really know what you're doing, you can disable the automatic saving of the status and other registers, with a GCC flag (ISR_NAKED). This can let you skip steps 5,6,8,9, in a context where you really need those cycles. The downside is you have to be absolutely certain you will either preserve any relevant registers, or can overwrite them without harm. \$\endgroup\$ Jan 17, 2015 at 6:12
  • 2
    \$\begingroup\$ That's interesting to know, but I would do assembly programming before using that flag. So dangerous... \$\endgroup\$
    – caveman
    Jan 17, 2015 at 6:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.