Electronic – ISR, volatile and ATOMIC_BLOCK

avr-gccisr

From my experience so far I am aware of the following to pay attention to when working with ISRs:

  • An ISR should complete quickly
  • Variables shared between an ISR and the main execution path should be declared volatile to avoid accesses to it being optimized away
  • Access to shared variables with 16 bit and more should be accessed in an ATOMIC_BLOCK because the 8 bit CPU cannot access such variables in a single cycle

But I am wondering about functions called from an ISR:

  1. Does calling a function from an ISR add any particular overhead? Looking at the disassembly, code in a function and the same code directly in the ISR yield the same instructions
  2. I suppose everything that applies to an ISR also applies to a function called from an ISR since it is the same execution path?
  3. If a function called from userspace contains an ATOMIC_BLOCK, is it valid to also call it from an ISR?

Best Answer

Non-AVR-specific answer:

Jumping into an ISR from a normal execution thread generally requires a context switch, where the current state of the CPU is saved somewhere (stack, shadow registers) so that the ISR can use the CPU as it needs to, and when it is finished, the ISR restores the context (so that to the main program, the CPU is in the exact state it was in prior to the ISR) and carries on. It is an asynchronous jump from normal execution - the program doesn't deliberately decide to branch, some external mechanism (a signal, some hardware, whatever) makes the decision.

An ordinary function call doesn't require a context switch per se. The code is following a known path, so there's no need to save/preserve all of the CPU registers. Depending on the function, you may need to pass arguments to it, and may read back a return value, but since you're not pre-empting normal execution (i.e. the program knows where it's going and when it's going there) and not expecting to return with everything as it was before the call, you're not doing a context switch.

If you don't see any context switching in your ISR code (could be pushes, pops, etc.) then perhaps the hardware takes care of that for you. On other devices (many Microchip parts, for example) you need to take care of the context switching in code. Generally I would expect that an ISR jump would require extra code to take care of the context switching vs. a naive void XXX(void) function call.

Calling a function from an ISR isn't so different from calling a function in userspace. The code will branch in much the same way as before, with arguments being passed and return values returned. If you are on a limited-stack device, however, beware: the function call will be pushing and popping stuff to and from the stack which likely is already maintaining the context of the code outside the ISR. Stack overflow is always a possibility with nested function calls (recursion is really bad for this).

An ATOMIC_BLOCK wrapper blocks interrupts from firing while a segment of code is being executed. If you're already in an ISR, I presume this would prevent the ISR from being pre-empted by another higher-priority ISR. I can understand why you may want do to this, so it seems valid to me (note: I am not an ARM expert)