Electronic – How to implement simple stack switching in PIC12/16 cores


I am trying to understand how real time operating systems work. I have looked at the source codes of some RTOSes. I want to learn by creating my simple RTOS, something like FLIRT.

I am using PIC16 series and XC8 C compiler with MPLABX. I also want to implement this very simple RTOS to PIC12 series.

So, I decided that I should start by learning how to manipulate stack (like supercat did in this answer) and I started searching and came across AN818 which is titled "Manipulating the Stack of the PIC18 Microcontroller". Cited from the application note:

Traditionally, the microcontroller stack has only been
used as a storage space for return addresses of subroutines
or interrupt routines, where all ‘push’ and ‘pop’
operations were hidden.

For the most part, users had
no direct access to the information on the stack. The
PIC18 microcontroller diverges from this tradition
slightly. With the new PIC18 core, users now have
access to the stack and can modify the stack pointer
and stack data directly.

I am confused. How come there are RTOSes made for PIC microcontrollers that work with PIC16 cores? For example, OSA RTOS is available for PIC12/16 with mikroC compiler.

Can you direct me to some resources, or if possible give examples, so that I can learn about stack switching?

Best Answer

Every RTOS for a PIC which does not have a software-addressable stack generally requires that all but one of the tasks must have its work divided into uninterruptable pieces which begin and end at the top stack level; the "task-yield" operation does not use a function call, but rather a sequence like

// This code is part of task C (assume for this example, there are tasks
// called A, B, C
  movlw JumpC4 & 255

Elsewhere in the code would be some code like:

  movwf nextJumpA  // Save state of task C
  // Now dispatch next instruction for task A
  movlw TaskB_Table >> 8
  movwf PCLATH
  movf  nextJumpB,w
  movwf PCL
  movwf nextJumpB  // Save state of task C
  // Now dispatch next instruction for task A
  movlw TaskC_Table >> 8
  movwf PCLATH
  movf  nextJumpC,w
  movwf PCL
  movwf nextJumpC  // Save state of task C
  // Now dispatch next instruction for task A
  movlw TaskA_Table >> 8
  movwf PCLATH
  movf  nextJumpA,w
  movwf PCL

At the end of the code, for each task, there would be a jump table; each table would have to fit within a 256-word page (and could thus have a maximum of 256 jumps)

JumpC0 : goto TargetC0
JumpC1 : goto TargetC1
JumpC2 : goto TargetC2
JumpC3 : goto TargetC3
JumpC4 : goto TargetC4

Effectively, the movlw at the start of the task-switch sequence loads the W register with the LSB of the address of the instruction at JumpC4. The code at TASK_SWITCH_FROM_C would stash that value someplace, and then dispatch the code for task A. Later, after TASK_SWITCH_FROM_B is executed, the stored JumpC4 address would be reloaded into W and the system would jump to the instruction pointed to thereby. That instruction would be a goto TargetC4, which would in turn resume execution at the instruction following the task-switch sequence. Note that task switching doesn't use the stack at all.

If one wanted to do a task switch within a called function, it might be possible to do so if that function's call and return were handled in a manner similar to the above (one would probably have to wrap the function call in a special macro to force the proper code to be generated). Note that the compiler itself wouldn't be capable of generating code like the above. Instead, macros in the source code would generate directives in the assembly-language file. A program supplied by the RTOS vendor would read the assembly-language file, look for those directives, and generate the appropriate vectoring code.