Electronic – Is there stack available in PIC ISRs

interruptsmplabxpicstack

I'm trying to better understand the interaction between the foreground (interrupt) and background (while (1)) loops in a PIC microcontroller. Specifically, a PIC16F1709 using XC8 v1.33. This documentation helps a bit, but I'm more interested in things at a C level (and less so hardware or assembly).

I know that we can neither return nor accept any parameters, so the only way to interact with the interrupt is with a global or module-level variable. Is it also true that we do NOT have any stack available to us in an ISR? So no local variables can be declared? Or is stack defined as depth of function calls?

My use of another function MY_FIFO_Push (while I know bad for ISRs), will consume some stack to pass the variables. I don't understand how this is working.

static volatile my_fifo_t my_fifo;

void MyIsr(void) {
  static uint8_t new_byte;

  // Grab a result from a hardware register
  new_byte = XXXRES;

  // Push the result onto a queue to be processed in background loop
  MY_FIFO_Push(&my_fifo, new_byte);

  // Clear the source of the interrupt
  XXXIF = 0;
}

...
void main(void) {

  while (1) {
    // Process the FIFO queue in the background loop
    // If there is new data on the queue
    //   Consume it
  }
}

Best Answer

Most Microchip PIC 8-bit micros have a hardware stack with a depth of only 8! (the size will vary for different PIC devices). Because the stack depth on these micros is so small it is used only for function calls. Each function call will consume one level of the hardware stack. The rest of the variables are pushed into a software stack which is automatically handled by the compiler.

So having an interrupt will automatically consume 1 level of the stack. Of course you can have variables declared in your interrupt, but they will be pushed into the software stack by the compiler.

Your microcontroller (PIC16F1709) has a 16-level hardware stack, which is a fairly good depth. In your sample code you only use 2 levels of the stack: one for the ISR and one for the MY_FIFO_Push function call from the ISR. So you're left with 14 more levels for nested function calls.

From Embedded Systems/PIC Microcontroller:

The PIC stack is a dedicated bank of registers (separate from programmer-accessible registers) that can only be used to store return addresses during a function call (or interrupt).

12 bit: A PIC microcontroller with a 12 bit core (the first generation of PIC microcontrollers) ( including most PIC10, some PIC12, a few PIC16 ) only has 2 registers in its hardware stack. Subroutines in a 12-bit PIC program may only be nested 2 deep, before the stack overflows, and data is lost. People who program 12 bit PICs spend a lot of effort working around this limitation. (These people are forced to rely heavily on techniques that avoid using the hardware stack. For example, macros, state machines, and software stacks). 14 bit: A PIC microcontroller with a 14 bit core (most PIC16) has 8 registers in the hardware stack. This makes function calls much easier to use, even though people who program them should be aware of some remaining gotchas [4]. 16 bit: A PIC microcontroller with a 16 bit core (all PIC18) has a "31-level deep" hardware stack depth. This is more than deep enough for most programs people write. Many algorithms involving pushing data to, then later pulling data from, some sort of stack. People who program such algorithms on the PIC must use a separate software stack for data (reminiscent of Forth). (People who use other microprocessors often share a single stack for both subroutine return addresses and this "stack data").

Call-tree analysis can be used to find the deepest possible subroutine nesting used by a program. (Unless the program uses w:recursion). As long as the deepest possible nesting of the "main" program, plus the deepest possible nesting of the interrupt routines, give a total sum less than the size of the stack of the microcontroller it runs on, then everything works fine. Some compilers automatically do such call-tree analysis, and if the hardware stack is insufficient, the compiler automatically switches over to using a "software stack". Assembly-language programmers are forced to do such analysis by hand.

From PIC stack overflow (if you read this article you might not want to use PICs in future projects. It hasn't stopped me, though :-) ):

The key thing to understand about the 8 bit PIC architecture is that the stack size is fixed. It varies from a depth of 2 for the really low end devices to 31 for the high end 8 bit devices. The most popular parts (such as the 16F877) have a stack size of 8. Every (r)call consumes a level, as does the interrupt handler. To add insult to injury, if you use the In Circuit Debugger (ICD) rather than a full blown ICE, then support for the ICD also consumes a level. So if you are using a 16 series part (for example) with an ICD and interrupts, then you have at most 6 levels available to you. What does this mean? Well if you are programming in assembly language (which when you get down to it was always the intention of the PIC designers) it means that you can nest function calls no more than six deep. If you are programming in C then depending on your compiler you may not even be able to nest functions this deep, particularly if you are using size optimization.